From 508fc87d2aa58446017d7c8591866f3c98e14ab0 Mon Sep 17 00:00:00 2001 From: Gert Driesen Date: Wed, 1 Nov 2023 11:33:42 +0100 Subject: [PATCH] Fix analyzer errors in Renci.SshNet and Renci.SshNet.TestTools.OpenSSH (#1229) * Fix analyzer errors in Renci.SshNet and Renci.SshNet.TestTools.OpenSSH. Suppress all errors in unit tests and integration tests. * Update unit tests now that we pass 'mode' as argument name when we throw ArgumentException. * Remove stale comment and add unit tests for SshData.ReadBytes(int length). * Remove unnessary suppression. * Remove Visual Studio magic. * Removed duplicate source file. * Clarified that suppression hides a false positive. * Remove suppressions for S2372. * Update ReadExtensionPair() to return concrete dictionary. --- .editorconfig | 50 ++ Directory.Build.props | 8 +- src/Renci.SshNet/.editorconfig | 123 ++- .../Abstractions/DiagnosticAbstraction.cs | 4 - .../Abstractions/DnsAbstraction.cs | 6 +- .../Abstractions/ReflectionAbstraction.cs | 2 +- .../Abstractions/SocketAbstraction.cs | 12 +- .../Abstractions/SocketExtensions.cs | 31 +- .../Abstractions/ThreadAbstraction.cs | 22 +- src/Renci.SshNet/AuthenticationMethod.cs | 2 + src/Renci.SshNet/AuthenticationResult.cs | 4 +- src/Renci.SshNet/BaseClient.cs | 15 +- src/Renci.SshNet/Channels/Channel.cs | 14 +- .../Channels/ChannelForwardedTcpip.cs | 8 +- src/Renci.SshNet/Channels/ChannelSession.cs | 2 +- src/Renci.SshNet/Channels/ChannelTypes.cs | 14 +- src/Renci.SshNet/Channels/ClientChannel.cs | 2 +- src/Renci.SshNet/Channels/IChannelSession.cs | 8 +- src/Renci.SshNet/CipherInfo.cs | 2 +- src/Renci.SshNet/ClientAuthentication.cs | 26 +- src/Renci.SshNet/Common/AsyncResult.cs | 46 -- .../Common/AsyncResult{TResult}.cs | 50 ++ src/Renci.SshNet/Common/BigInteger.cs | 175 +++- .../Common/ChannelDataEventArgs.cs | 10 +- .../Common/ChannelRequestEventArgs.cs | 6 + src/Renci.SshNet/Common/DerData.cs | 2 +- src/Renci.SshNet/Common/Extensions.cs | 3 +- src/Renci.SshNet/Common/HostKeyEventArgs.cs | 44 +- .../Common/NetConfServerException.cs | 2 +- src/Renci.SshNet/Common/ObjectIdentifier.cs | 2 + src/Renci.SshNet/Common/PipeStream.cs | 45 +- src/Renci.SshNet/Common/SemaphoreLight.cs | 4 +- .../Common/SshConnectionException.cs | 11 + src/Renci.SshNet/Common/SshData.cs | 46 +- src/Renci.SshNet/Common/SshDataStream.cs | 36 +- src/Renci.SshNet/Common/TerminalModes.cs | 6 +- .../Compression/CompressionMode.cs | 2 +- src/Renci.SshNet/Compression/Compressor.cs | 12 +- src/Renci.SshNet/Compression/Zlib.cs | 4 +- src/Renci.SshNet/Compression/ZlibOpenSsh.cs | 6 +- src/Renci.SshNet/Compression/ZlibStream.cs | 9 + src/Renci.SshNet/Connection/IConnector.cs | 18 + .../Connection/IProtocolVersionExchange.cs | 14 +- src/Renci.SshNet/Connection/ISocketFactory.cs | 14 + .../Connection/ProtocolVersionExchange.cs | 12 +- src/Renci.SshNet/Connection/ProxyConnector.cs | 35 +- src/Renci.SshNet/Connection/SocketFactory.cs | 16 +- .../Connection/Socks4Connector.cs | 32 +- .../Connection/Socks5Connector.cs | 62 +- src/Renci.SshNet/ConnectionInfo.cs | 20 +- src/Renci.SshNet/ForwardedPortDynamic.cs | 6 +- src/Renci.SshNet/ForwardedPortStatus.cs | 2 + src/Renci.SshNet/HashInfo.cs | 4 +- src/Renci.SshNet/IBaseClient.cs | 7 +- src/Renci.SshNet/IClientAuthentication.cs | 18 +- src/Renci.SshNet/IConnectionInfo.cs | 29 - src/Renci.SshNet/IConnectionInfoInternal.cs | 37 + src/Renci.SshNet/IForwardedPort.cs | 30 - src/Renci.SshNet/ISession.cs | 5 +- src/Renci.SshNet/ISftpClient.cs | 42 +- src/Renci.SshNet/ISubsystemSession.cs | 10 +- .../KeyboardInteractiveConnectionInfo.cs | 3 +- .../Messages/Authentication/FailureMessage.cs | 8 +- .../InformationResponseMessage.cs | 3 +- .../Messages/Authentication/RequestMessage.cs | 1 - .../Authentication/RequestMessageNone.cs | 2 +- .../Connection/ChannelCloseMessage.cs | 1 - .../Messages/Connection/ChannelEofMessage.cs | 1 - .../Connection/ChannelFailureMessage.cs | 1 - .../Connection/ChannelOpen/ChannelOpenInfo.cs | 2 +- .../ChannelOpen/DirectTcpipChannelInfo.cs | 4 +- .../ChannelOpen/ForwardedTcpipChannelInfo.cs | 28 +- .../ChannelOpen/SessionChannelOpenInfo.cs | 4 +- .../ChannelOpen/X11ChannelOpenInfo.cs | 4 +- .../Connection/ChannelOpenFailureMessage.cs | 1 - .../Connection/ChannelOpenFailureReasons.cs | 11 +- .../ChannelRequest/BreakRequestInfo.cs | 8 +- .../ChannelRequest/ChannelRequestMessage.cs | 7 +- .../ChannelRequest/EndOfWriteRequestInfo.cs | 4 +- .../EnvironmentVariableRequestInfo.cs | 8 +- .../ChannelRequest/ExitSignalRequestInfo.cs | 6 +- .../ChannelRequest/ExitStatusRequestInfo.cs | 4 +- .../ChannelRequest/KeepAliveRequestInfo.cs | 6 +- ...alInfo.cs => PseudoTerminalRequestInfo.cs} | 2 +- .../Connection/ChannelRequest/RequestInfo.cs | 2 +- .../ChannelRequest/ShellRequestInfo.cs | 4 +- .../ChannelRequest/SignalRequestInfo.cs | 2 +- .../ChannelRequest/SubsystemRequestInfo.cs | 4 +- .../ChannelRequest/WindowChangeRequestInfo.cs | 4 +- .../X11ForwardingRequestInfo.cs | 2 +- .../ChannelRequest/XonXoffRequestInfo.cs | 4 +- .../Connection/ChannelSuccessMessage.cs | 1 - .../Connection/GlobalRequestMessage.cs | 2 +- .../Messages/Connection/GlobalRequestName.cs | 5 +- src/Renci.SshNet/Messages/Message.cs | 129 +-- src/Renci.SshNet/Messages/MessageAttribute.cs | 9 +- src/Renci.SshNet/Messages/ServiceName.cs | 6 +- .../Messages/Transport/DebugMessage.cs | 2 +- .../Messages/Transport/DisconnectReason.cs | 49 +- .../Transport/IKeyExchangedAllowed.cs | 4 +- .../Messages/Transport/IgnoreMessage.cs | 30 +- .../KeyExchangeDhGroupExchangeGroup.cs | 4 +- .../Transport/KeyExchangeDhReplyMessage.cs | 2 +- .../Transport/KeyExchangeEcdhInitMessage.cs | 2 +- .../Transport/KeyExchangeEcdhReplyMessage.cs | 4 +- src/Renci.SshNet/Netconf/INetConfSession.cs | 22 +- src/Renci.SshNet/Netconf/NetConfSession.cs | 4 + .../PasswordAuthenticationMethod.cs | 2 +- src/Renci.SshNet/PasswordConnectionInfo.cs | 4 +- .../PrivateKeyAuthenticationMethod.cs | 4 +- src/Renci.SshNet/PrivateKeyFile.cs | 31 +- .../Properties/CommonAssemblyInfo.cs | 5 +- src/Renci.SshNet/ProxyTypes.cs | 19 +- src/Renci.SshNet/RemotePathTransformation.cs | 6 +- src/Renci.SshNet/ScpClient.cs | 8 + .../Security/BouncyCastle/math/BigInteger.cs | 4 + .../BouncyCastle/security/SecureRandom.cs | 2 + .../Security/CertificateHostAlgorithm.cs | 4 +- .../Security/Cryptography/AsymmetricCipher.cs | 2 +- .../Security/Cryptography/BlockCipher.cs | 8 +- .../Cryptography/CipherDigitalSignature.cs | 4 +- .../Cryptography/Ciphers/AesCipher.cs | 129 +-- .../Cryptography/Ciphers/Arc4Cipher.cs | 16 +- .../Cryptography/Ciphers/BlowfishCipher.cs | 21 +- .../Cryptography/Ciphers/CastCipher.cs | 21 +- .../Cryptography/Ciphers/CipherMode.cs | 6 +- .../Cryptography/Ciphers/CipherPadding.cs | 2 +- .../Cryptography/Ciphers/DesCipher.cs | 27 +- .../Ciphers/Modes/CbcCipherMode.cs | 2 +- .../Ciphers/Modes/CfbCipherMode.cs | 2 +- .../Ciphers/Modes/CtrCipherMode.cs | 32 +- .../Ciphers/Modes/OfbCipherMode.cs | 29 +- .../Ciphers/Paddings/PKCS7Padding.cs | 2 +- .../Cryptography/Ciphers/RsaCipher.cs | 4 +- .../Cryptography/Ciphers/SerpentCipher.cs | 2 +- .../Cryptography/Ciphers/TripleDesCipher.cs | 8 +- .../Cryptography/Ciphers/TwofishCipher.cs | 236 +++--- .../Security/Cryptography/DigitalSignature.cs | 6 +- .../Cryptography/DsaDigitalSignature.cs | 4 +- .../Security/Cryptography/DsaKey.cs | 2 +- .../Cryptography/ED25519DigitalSignature.cs | 2 +- .../Security/Cryptography/ED25519Key.cs | 14 +- .../Cryptography/EcdsaDigitalSignature.cs | 138 ++-- .../Security/Cryptography/EcdsaKey.cs | 28 +- .../Security/Cryptography/HMACMD5.cs | 13 +- .../Security/Cryptography/HMACSHA1.cs | 8 + .../Security/Cryptography/HMACSHA256.cs | 8 +- .../Security/Cryptography/HMACSHA384.cs | 7 +- .../Security/Cryptography/HMACSHA512.cs | 7 +- src/Renci.SshNet/Security/Cryptography/Key.cs | 2 + .../Cryptography/RsaDigitalSignature.cs | 5 +- .../Security/Cryptography/RsaKey.cs | 39 +- .../Security/GroupExchangeHashData.cs | 1 - src/Renci.SshNet/Security/KeyExchange.cs | 4 +- .../Security/KeyExchangeDiffieHellman.cs | 6 +- src/Renci.SshNet/Security/KeyExchangeEC.cs | 6 +- ...ExchangeHash.cs => KeyExchangeHashData.cs} | 0 src/Renci.SshNet/Security/KeyHostAlgorithm.cs | 20 +- src/Renci.SshNet/Session.cs | 55 +- src/Renci.SshNet/Sftp/Flags.cs | 27 +- src/Renci.SshNet/Sftp/ISftpFile.cs | 16 +- src/Renci.SshNet/Sftp/ISftpFileReader.cs | 14 + src/Renci.SshNet/Sftp/ISftpResponseFactory.cs | 13 + src/Renci.SshNet/Sftp/ISftpSession.cs | 175 +++- .../Sftp/Requests/SftpExtendedRequest.cs | 7 +- .../ExtendedReplies/StatVfsReplyInfo.cs | 3 +- .../Sftp/Responses/SftpDataResponse.cs | 2 +- .../Responses/SftpExtendedReplyResponse.cs | 3 +- .../Sftp/Responses/SftpHandleResponse.cs | 2 +- .../Sftp/Responses/SftpResponse.cs | 2 +- .../Sftp/SftpDownloadAsyncResult.cs | 2 +- src/Renci.SshNet/Sftp/SftpFileAttributes.cs | 149 ++-- src/Renci.SshNet/Sftp/SftpFileReader.cs | 8 +- src/Renci.SshNet/Sftp/SftpFileStream.cs | 45 +- .../Sftp/SftpFileSystemInformation.cs | 8 +- src/Renci.SshNet/Sftp/SftpSession.cs | 761 ++++++++++++------ src/Renci.SshNet/Sftp/StatusCodes.cs | 100 ++- src/Renci.SshNet/SftpClient.cs | 184 +++-- src/Renci.SshNet/Shell.cs | 6 +- src/Renci.SshNet/ShellStream.cs | 411 +++++----- src/Renci.SshNet/SshClient.cs | 18 +- src/Renci.SshNet/SshCommand.cs | 57 +- src/Renci.SshNet/SshMessageFactory.cs | 119 +-- src/Renci.SshNet/SubsystemSession.cs | 20 +- test/.editorconfig | 29 +- test/Renci.SshNet.Benchmarks/.editorconfig | 72 ++ .../.editorconfig | 312 +++++++ .../HostConfig.cs | 14 - .../HostEntry.cs | 18 + .../RemoteSshd.cs | 217 +---- .../RemoteSshdConfig.cs | 217 +++++ .../.editorconfig | 23 + test/Renci.SshNet.TestTools.OpenSSH/Cipher.cs | 2 +- .../Formatters/BooleanFormatter.cs | 2 +- .../Formatters/Int32Formatter.cs | 2 +- .../Formatters/LogLevelFormatter.cs | 2 +- .../Formatters/MatchFormatter.cs | 2 +- .../Formatters/SubsystemFormatter.cs | 2 +- .../HostKeyAlgorithm.cs | 36 +- .../KeyExchangeAlgorithm.cs | 3 +- test/Renci.SshNet.TestTools.OpenSSH/Match.cs | 7 +- .../MessageAuthenticationCodeAlgorithm.cs | 4 +- .../PublicKeyAlgorithm.cs | 2 +- .../SshdConfig.cs | 186 +++-- .../Subsystem.cs | 20 +- test/Renci.SshNet.Tests/.editorconfig | 352 ++++++++ .../Classes/Common/SshDataTest.cs | 72 ++ ...Test_Ctor_FileModeAppend_FileAccessRead.cs | 5 +- ...Ctor_FileModeAppend_FileAccessReadWrite.cs | 5 +- ...t_Ctor_FileModeCreateNew_FileAccessRead.cs | 5 +- ...Test_Ctor_FileModeCreate_FileAccessRead.cs | 5 +- ...st_Ctor_FileModeTruncate_FileAccessRead.cs | 5 +- ...OpenAsync_FileModeAppend_FileAccessRead.cs | 5 +- ...sync_FileModeAppend_FileAccessReadWrite.cs | 5 +- ...nAsync_FileModeCreateNew_FileAccessRead.cs | 5 +- ...OpenAsync_FileModeCreate_FileAccessRead.cs | 5 +- ...enAsync_FileModeTruncate_FileAccessRead.cs | 5 +- 217 files changed, 4326 insertions(+), 2194 deletions(-) create mode 100644 src/Renci.SshNet/Common/AsyncResult{TResult}.cs create mode 100644 src/Renci.SshNet/IConnectionInfoInternal.cs rename src/Renci.SshNet/Messages/Connection/ChannelRequest/{PseudoTerminalInfo.cs => PseudoTerminalRequestInfo.cs} (98%) rename src/Renci.SshNet/Security/{KeyExchangeHash.cs => KeyExchangeHashData.cs} (100%) create mode 100644 test/Renci.SshNet.Benchmarks/.editorconfig create mode 100644 test/Renci.SshNet.IntegrationTests/HostEntry.cs create mode 100644 test/Renci.SshNet.IntegrationTests/RemoteSshdConfig.cs create mode 100644 test/Renci.SshNet.TestTools.OpenSSH/.editorconfig diff --git a/.editorconfig b/.editorconfig index 18f2172f..944a6bf0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -77,6 +77,12 @@ dotnet_diagnostic.S1172.severity = none # This is a duplicate of IDE0059. dotnet_diagnostic.S1481.severity = none +# S1854: Unused assignments should be removed +# https://rules.sonarsource.com/csharp/RSPEC-1854 +# +# This is a duplicate of IDE0059. +dotnet_diagnostic.S1854.severity = none + # S2259: Null pointers should not be dereferenced # https://rules.sonarsource.com/csharp/RSPEC-2259 # @@ -168,6 +174,12 @@ dotnet_diagnostic.S3928.severity = none # This is a duplicate of CA2002, and partial duplicate of MA0064. dotnet_diagnostic.S3998.severity = none +# S4070: Non-flags enums should not be marked with "FlagsAttribute" +# https://rules.sonarsource.com/csharp/RSPEC-4070 +# +# This is a duplicate of MA0062. +dotnet_diagnostic.S4070.severity = none + # S4456: Parameter validation in yielding methods should be wrapped # https://rules.sonarsource.com/csharp/RSPEC-4456 # @@ -364,6 +376,14 @@ dotnet_diagnostic.MA0018.severity = none # No strong need for this, and may negatively affect performance. dotnet_diagnostic.MA0021.severity = none +# MA0025: Implement the functionality instead of throwing NotImplementedException +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0031.md +dotnet_diagnostic.MA0025.severity = none + +# MA0026: Fix TODO comment +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0026.md +dotnet_diagnostic.MA0026.severity = suggestion + # MA0031: Optimize Enumerable.Count() usage # https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0031.md # @@ -436,6 +456,12 @@ dotnet_diagnostic.MA0112.severity = error # instead of a base class or interface. dotnet_diagnostic.CA1002.severity = none +# CA1003: Use generic event handler instances +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1003 +# +# Similar to MA0046. +dotnet_diagnostic.CA1003.severity = none + # CA1008: Enums should have zero value # # TODO: To be discussed. Having a zero value offers a performance advantage. @@ -460,6 +486,10 @@ dotnet_diagnostic.CA1051.severity = none # By default, this diagnostic is only reported for public types. dotnet_code_quality.CA1052.api_surface = all +# CA1065: Do not raise exceptions in unexpected locations +# https://learn.microsoft.com/en-US/dotnet/fundamentals/code-analysis/quality-rules/ca1065 +dotnet_diagnostic.CA1065.severity = none + # CA1303: Do not pass literals as localized parameters # # We don't care about localization. @@ -473,6 +503,10 @@ dotnet_diagnostic.CA1303.severity = none # Submitted https://github.com/dotnet/roslyn-analyzers/issues/6096 to fix CA1305. dotnet_diagnostic.CA1305.severity = none +# CA1309: Use ordinal StringComparison +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1309 +dotnet_diagnostic.CA1309.severity = none + # CA1510: Use ArgumentNullException throw helper # # This is only available in .NET 6.0 and higher. We'd need to use conditional compilation to only @@ -577,6 +611,22 @@ dotnet_diagnostic.IDE0130.severity = none # var inputPath = originalDossierPathList.Find(x => x.id == updatedPath.id) ?? throw new PcsException($"Path id ({updatedPath.id}) unknown in PCS for dossier id {dossierFromTs.dossier.id}", updatedPath.id); dotnet_diagnostic.IDE0270.severity = none +# IDE0290: Use primary constructor +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0290 +dotnet_diagnostic.IDE0290.severity = none + +# IDE0300: Collection initialization can be simplified +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0300 +# +# TODO: Discuss whether we want to start using this +dotnet_diagnostic.IDE0300.severity = none + +# IDE0301: Simplify collection initialization +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0301 +# +# TODO: Discuss whether we want to start using this +dotnet_diagnostic.IDE0301.severity = none + #### .NET Compiler Platform code style rules #### ### Language rules ### diff --git a/Directory.Build.props b/Directory.Build.props index 100efff9..4ffd15e3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,7 @@ Code analysis properties. --> - false + true preview-All true @@ -34,11 +34,9 @@ Use fixed version of analyzers. --> - + + diff --git a/src/Renci.SshNet/.editorconfig b/src/Renci.SshNet/.editorconfig index 97f5eb12..c5f4bdf6 100644 --- a/src/Renci.SshNet/.editorconfig +++ b/src/Renci.SshNet/.editorconfig @@ -1,5 +1,38 @@ [*.cs] +#### Sonar rules #### + +# S1264: A "while" loop should be used instead of a "for" loop +# https://rules.sonarsource.com/csharp/RSPEC-1264 +dotnet_diagnostic.S1264.severity = none + +# S1450: Private fields only used as local variables in methods should become local variables +# https://rules.sonarsource.com/csharp/RSPEC-1450 +# +# TODO: Re-enable when the following issue is resolved: +# https://github.com/SonarSource/sonar-dotnet/issues/8239 +dotnet_diagnostic.S1450.severity = none + +# S2372: Exceptions should not be thrown from property getters +# https://rules.sonarsource.com/csharp/RSPEC-2372/ +dotnet_diagnostic.S2372.severity = none + +# S2583: Conditionally executed code should be reachable +# https://rules.sonarsource.com/csharp/RSPEC-2583/ +# +# TODO: Re-enable when the following issue is resolved: +# https://github.com/SonarSource/sonar-dotnet/issues/8264 +dotnet_diagnostic.S2583.severity = none + +# S2589: Boolean expressions should not be gratuitous +# https://rules.sonarsource.com/csharp/RSPEC-2589/ +# +# TODO: Re-enable when the following issue is resolved: +# https://github.com/SonarSource/sonar-dotnet/issues/8262 +dotnet_diagnostic.S2589.severity = none + +dotnet_diagnostic.S2372.severity = none + #### SYSLIB diagnostics #### # SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time @@ -7,32 +40,114 @@ # TODO: Remove this when https://github.com/sshnet/SSH.NET/issues/1131 is implemented. dotnet_diagnostic.SYSLIB1045.severity = none -### StyleCop Analyzers rules ### +#### StyleCop Analyzers rules #### + +# SA1123: Do not place regions within elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1123.md +dotnet_diagnostic.SA1123.severity = none + +# SA1124: Do not use regions +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1124.md +dotnet_diagnostic.SA1124.severity = none # SA1202: Elements must be ordered by access # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1202.md dotnet_diagnostic.SA1202.severity = none +# SA1204: Static elements must appear before instance elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1204.md +dotnet_diagnostic.SA1204.severity = none + +# SA1310: Field names must not contain underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1310.md +#dotnet_diagnostic.SA1310.severity = none + +# SA1312: Variable names should begin with lower-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md +dotnet_diagnostic.SA1312.severity = none + +# SA1636: File header copyright text should match +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1636.md +dotnet_diagnostic.SA1636.severity = none + +# SA1643: Destructor summary documentation must begin with standard text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1643.md +dotnet_diagnostic.SA1643.severity = none + #### Meziantou.Analyzer rules #### +# MA0001: StringComparison is missing +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0001.md +dotnet_diagnostic.MA0001.severity = none + +# MA0011: IFormatProvider is missing +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0011.md +# +# TODO: Remove exclusion when issues are fixed +dotnet_diagnostic.MA0011.severity = none + +# MA0015: Specify the parameter name in ArgumentException +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0015.md +# +# TODO: Remove exclusion when issues are fixed +dotnet_diagnostic.MA0015.severity = none + +# MA0050: Validate arguments correctly in iterator methods +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0050.md +# +# TODO: Re-enable when https://github.com/meziantou/Meziantou.Analyzer/issues/617 is fixed +dotnet_diagnostic.MA0050.severity = none + # MA0053: Make class sealed # https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0053.md MA0053.public_class_should_be_sealed = false +# MA0055: Do not use finalizer +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0055.md +# +# TODO: Remove exclusion when issues are fixed +dotnet_diagnostic.MA0055.severity = none + +# MA0110: Use the Regex source generator +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0110.md +dotnet_diagnostic.MA0110.severity = none + #### .NET Compiler Platform analysers rules #### # CA1030: Use events where appropriate # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1030 -dotnet_diagnostic.CA10310.severity = none +dotnet_diagnostic.CA1030.severity = none # CA1031: Do not catch general exception types # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1031 dotnet_diagnostic.CA1031.severity = none +# CA1062: Validate arguments of public methods +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1062 +# +# TODO: Remove exclusion when issues are fixed +dotnet_diagnostic.CA1062.severity = none + +# CA1307: Specify StringComparison for clarity +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1307 +dotnet_diagnostic.CA1307.severity = none + +# CA1716: Identifiers should not match keywords +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1716 +dotnet_diagnostic.CA1716.severity = none + +# CA1822: Mark members as static +# https://learn.microsoft.com/en-US/dotnet/fundamentals/code-analysis/quality-rules/ca1822 +dotnet_code_quality.CA1822.api_surface = private,internal + # CA2213: Disposable fields should be disposed # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2213 dotnet_diagnostic.CA2213.severity = none +# CA3075: Insecure DTD Processing +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca3075 +dotnet_diagnostic.CA3075.severity = none + # IDE0004: Types that own disposable fields should be disposable # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0004 dotnet_diagnostic.IDE0004.severity = none @@ -40,3 +155,7 @@ dotnet_diagnostic.IDE0004.severity = none # IDE0048: Add parentheses for clarity # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0047 dotnet_diagnostic.IDE0048.severity = none + +# IDE0305: Collection initialization can be simplified +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0305 +dotnet_diagnostic.IDE0305.severity = none diff --git a/src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs b/src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs index 18275e72..014d7068 100644 --- a/src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs +++ b/src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs @@ -22,11 +22,7 @@ namespace Renci.SshNet.Abstractions public static void Log(string text) { Loggging.TraceEvent(TraceEventType.Verbose, -#if NET6_0_OR_GREATER System.Environment.CurrentManagedThreadId, -#else - System.Threading.Thread.CurrentThread.ManagedThreadId, -#endif // NET6_0_OR_GREATER text); } } diff --git a/src/Renci.SshNet/Abstractions/DnsAbstraction.cs b/src/Renci.SshNet/Abstractions/DnsAbstraction.cs index 0a3f98ec..30a10fe8 100644 --- a/src/Renci.SshNet/Abstractions/DnsAbstraction.cs +++ b/src/Renci.SshNet/Abstractions/DnsAbstraction.cs @@ -25,7 +25,7 @@ namespace Renci.SshNet.Abstractions /// /// Returns the Internet Protocol (IP) addresses for the specified host. /// - /// The host name or IP address to resolve + /// The host name or IP address to resolve. /// /// An array of type that holds the IP addresses for the host that /// is specified by the parameter. @@ -34,7 +34,7 @@ namespace Renci.SshNet.Abstractions /// An error is encountered when resolving . public static IPAddress[] GetHostAddresses(string hostNameOrAddress) { - // TODO Eliminate sync variant, and implement timeout + /* TODO Eliminate sync variant, and implement timeout */ #if FEATURE_DNS_SYNC return Dns.GetHostAddresses(hostNameOrAddress); @@ -92,7 +92,7 @@ namespace Renci.SshNet.Abstractions /// /// Returns the Internet Protocol (IP) addresses for the specified host. /// - /// The host name or IP address to resolve + /// The host name or IP address to resolve. /// /// A task with result of an array of type that holds the IP addresses for the host that /// is specified by the parameter. diff --git a/src/Renci.SshNet/Abstractions/ReflectionAbstraction.cs b/src/Renci.SshNet/Abstractions/ReflectionAbstraction.cs index 1140c9a6..d9e15a50 100644 --- a/src/Renci.SshNet/Abstractions/ReflectionAbstraction.cs +++ b/src/Renci.SshNet/Abstractions/ReflectionAbstraction.cs @@ -7,7 +7,7 @@ namespace Renci.SshNet.Abstractions internal static class ReflectionAbstraction { public static IEnumerable GetCustomAttributes(this Type type, bool inherit) - where T:Attribute + where T : Attribute { var attributes = type.GetCustomAttributes(typeof(T), inherit); return attributes.Cast(); diff --git a/src/Renci.SshNet/Abstractions/SocketAbstraction.cs b/src/Renci.SshNet/Abstractions/SocketAbstraction.cs index f325fdc3..f8b28916 100644 --- a/src/Renci.SshNet/Abstractions/SocketAbstraction.cs +++ b/src/Renci.SshNet/Abstractions/SocketAbstraction.cs @@ -261,11 +261,6 @@ namespace Renci.SshNet.Abstractions return buffer; } - public static Task ReadAsync(Socket socket, byte[] buffer, int offset, int length, CancellationToken cancellationToken) - { - return socket.ReceiveAsync(buffer, offset, length, cancellationToken); - } - /// /// Receives data from a bound into a receive buffer. /// @@ -293,7 +288,7 @@ namespace Renci.SshNet.Abstractions var totalBytesRead = 0; var totalBytesToRead = size; - socket.ReceiveTimeout = (int)readTimeout.TotalMilliseconds; + socket.ReceiveTimeout = (int) readTimeout.TotalMilliseconds; do { @@ -330,6 +325,11 @@ namespace Renci.SshNet.Abstractions return totalBytesRead; } + public static Task ReadAsync(Socket socket, byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + return socket.ReceiveAsync(buffer, offset, length, cancellationToken); + } + public static void Send(Socket socket, byte[] data) { Send(socket, data, 0, data.Length); diff --git a/src/Renci.SshNet/Abstractions/SocketExtensions.cs b/src/Renci.SshNet/Abstractions/SocketExtensions.cs index a51d0cb8..9edfbf94 100644 --- a/src/Renci.SshNet/Abstractions/SocketExtensions.cs +++ b/src/Renci.SshNet/Abstractions/SocketExtensions.cs @@ -10,24 +10,25 @@ namespace Renci.SshNet.Abstractions // Async helpers based on https://devblogs.microsoft.com/pfxteam/awaiting-socket-operations/ internal static class SocketExtensions { - private sealed class SocketAsyncEventArgsAwaitable : SocketAsyncEventArgs, INotifyCompletion + private sealed class AwaitableSocketAsyncEventArgs : SocketAsyncEventArgs, INotifyCompletion { private static readonly Action SENTINEL = () => { }; private bool _isCancelled; private Action _continuationAction; - public SocketAsyncEventArgsAwaitable() + public AwaitableSocketAsyncEventArgs() { - Completed += delegate { SetCompleted(); }; + Completed += (sender, e) => SetCompleted(); } - public SocketAsyncEventArgsAwaitable ExecuteAsync(Func func) + public AwaitableSocketAsyncEventArgs ExecuteAsync(Func func) { if (!func(this)) { SetCompleted(); } + return this; } @@ -48,7 +49,9 @@ namespace Renci.SshNet.Abstractions SetCompleted(); } - public SocketAsyncEventArgsAwaitable GetAwaiter() +#pragma warning disable S1144 // Unused private types or members should be removed + public AwaitableSocketAsyncEventArgs GetAwaiter() +#pragma warning restore S1144 // Unused private types or members should be removed { return this; } @@ -64,7 +67,9 @@ namespace Renci.SshNet.Abstractions } } +#pragma warning disable S1144 // Unused private types or members should be removed public void GetResult() +#pragma warning restore S1144 // Unused private types or members should be removed { if (_isCancelled) { @@ -88,11 +93,15 @@ namespace Renci.SshNet.Abstractions { cancellationToken.ThrowIfCancellationRequested(); - using (var args = new SocketAsyncEventArgsAwaitable()) + using (var args = new AwaitableSocketAsyncEventArgs()) { args.RemoteEndPoint = remoteEndpoint; - using (cancellationToken.Register(o => ((SocketAsyncEventArgsAwaitable)o).SetCancelled(), args, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(o => ((AwaitableSocketAsyncEventArgs)o).SetCancelled(), args, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(o => ((AwaitableSocketAsyncEventArgs) o).SetCancelled(), args, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { await args.ExecuteAsync(socket.ConnectAsync); } @@ -103,11 +112,15 @@ namespace Renci.SshNet.Abstractions { cancellationToken.ThrowIfCancellationRequested(); - using (var args = new SocketAsyncEventArgsAwaitable()) + using (var args = new AwaitableSocketAsyncEventArgs()) { args.SetBuffer(buffer, offset, length); - using (cancellationToken.Register(o => ((SocketAsyncEventArgsAwaitable)o).SetCancelled(), args, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(o => ((AwaitableSocketAsyncEventArgs) o).SetCancelled(), args, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(o => ((AwaitableSocketAsyncEventArgs) o).SetCancelled(), args, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { await args.ExecuteAsync(socket.ReceiveAsync); } diff --git a/src/Renci.SshNet/Abstractions/ThreadAbstraction.cs b/src/Renci.SshNet/Abstractions/ThreadAbstraction.cs index 0c9e3b64..b3fe2bb4 100644 --- a/src/Renci.SshNet/Abstractions/ThreadAbstraction.cs +++ b/src/Renci.SshNet/Abstractions/ThreadAbstraction.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; namespace Renci.SshNet.Abstractions { @@ -10,18 +12,28 @@ namespace Renci.SshNet.Abstractions /// The number of milliseconds for which the thread is suspended. public static void Sleep(int millisecondsTimeout) { - System.Threading.Thread.Sleep(millisecondsTimeout); + Thread.Sleep(millisecondsTimeout); } - public static void ExecuteThreadLongRunning(Action action) + /// + /// Creates and starts a long-running for the specified . + /// + /// The to start. + /// is . + /// + /// A task that represents the execution of the specified . + /// + public static Task ExecuteThreadLongRunning(Action action) { if (action is null) { throw new ArgumentNullException(nameof(action)); } - var taskCreationOptions = System.Threading.Tasks.TaskCreationOptions.LongRunning; - _ = System.Threading.Tasks.Task.Factory.StartNew(action, taskCreationOptions); + return Task.Factory.StartNew(action, + CancellationToken.None, + TaskCreationOptions.LongRunning, + TaskScheduler.Current); } /// @@ -35,7 +47,7 @@ namespace Renci.SshNet.Abstractions throw new ArgumentNullException(nameof(action)); } - _ = System.Threading.ThreadPool.QueueUserWorkItem(o => action()); + _ = ThreadPool.QueueUserWorkItem(o => action()); } } } diff --git a/src/Renci.SshNet/AuthenticationMethod.cs b/src/Renci.SshNet/AuthenticationMethod.cs index b7ad7b68..16783f1b 100644 --- a/src/Renci.SshNet/AuthenticationMethod.cs +++ b/src/Renci.SshNet/AuthenticationMethod.cs @@ -13,7 +13,9 @@ namespace Renci.SshNet /// /// The name of the authentication method. /// +#pragma warning disable CA2119 // Seal methods that satisfy private interfaces public abstract string Name { get; } +#pragma warning restore CA2119 // Seal methods that satisfy private interfaces /// /// Gets connection username. diff --git a/src/Renci.SshNet/AuthenticationResult.cs b/src/Renci.SshNet/AuthenticationResult.cs index 71414966..9dc54648 100644 --- a/src/Renci.SshNet/AuthenticationResult.cs +++ b/src/Renci.SshNet/AuthenticationResult.cs @@ -1,7 +1,7 @@ namespace Renci.SshNet { /// - /// Represents possible authentication methods results + /// Represents possible authentication methods results. /// public enum AuthenticationResult { @@ -9,10 +9,12 @@ /// Authentication was successful. /// Success, + /// /// Authentication completed with partial success. /// PartialSuccess, + /// /// Authentication failed. /// diff --git a/src/Renci.SshNet/BaseClient.cs b/src/Renci.SshNet/BaseClient.cs index ca91a90f..ddd434c2 100644 --- a/src/Renci.SshNet/BaseClient.cs +++ b/src/Renci.SshNet/BaseClient.cs @@ -12,7 +12,7 @@ namespace Renci.SshNet /// /// Serves as base class for client implementations, provides common client functionality. /// - public abstract class BaseClient : IBaseClient, IDisposable + public abstract class BaseClient : IBaseClient { /// /// Holds value indicating whether the connection info is owned by this client. @@ -341,7 +341,9 @@ namespace Renci.SshNet /// intervals. /// /// The method was called after the client was disposed. +#pragma warning disable S1133 // Deprecated code should be removed [Obsolete("Use KeepAliveInterval to send a keep-alive message at regular intervals.")] +#pragma warning restore S1133 // Deprecated code should be removed public void SendKeepAlive() { CheckDisposed(); @@ -393,8 +395,6 @@ namespace Renci.SshNet /// public void Dispose() { - DiagnosticAbstraction.Log("Disposing client."); - Dispose(disposing: true); GC.SuppressFinalize(this); } @@ -412,14 +412,17 @@ namespace Renci.SshNet if (disposing) { + DiagnosticAbstraction.Log("Disposing client."); + Disconnect(); - if (_ownsConnectionInfo && _connectionInfo != null) + if (_ownsConnectionInfo && _connectionInfo is not null) { if (_connectionInfo is IDisposable connectionInfoDisposable) { connectionInfoDisposable.Dispose(); } + _connectionInfo = null; } @@ -433,10 +436,14 @@ namespace Renci.SshNet /// THe current instance is disposed. protected void CheckDisposed() { +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_isDisposed, this); +#else if (_isDisposed) { throw new ObjectDisposedException(GetType().FullName); } +#endif // NET7_0_OR_GREATER } /// diff --git a/src/Renci.SshNet/Channels/Channel.cs b/src/Renci.SshNet/Channels/Channel.cs index f68a3947..580c63e0 100644 --- a/src/Renci.SshNet/Channels/Channel.cs +++ b/src/Renci.SshNet/Channels/Channel.cs @@ -16,13 +16,14 @@ namespace Renci.SshNet.Channels internal abstract class Channel : IChannel { private readonly object _serverWindowSizeLock = new object(); + private readonly object _messagingLock = new object(); private readonly uint _initialWindowSize; + private readonly ISession _session; private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(initialState: false); private EventWaitHandle _channelServerWindowAdjustWaitHandle = new ManualResetEvent(initialState: false); private uint? _remoteWindowSize; private uint? _remoteChannelNumber; private uint? _remotePacketSize; - private readonly ISession _session; private bool _isDisposed; /// @@ -497,7 +498,7 @@ namespace Renci.SshNet.Channels throw CreateChannelClosedException(); } - lock (this) + lock (_messagingLock) { _session.SendMessage(new ChannelEofMessage(RemoteChannelNumber)); _eofMessageSent = true; @@ -525,7 +526,7 @@ namespace Renci.SshNet.Channels * message causing the server to disconnect the session. */ - lock (this) + lock (_messagingLock) { // Send EOF message first the following conditions are met: // * we have not sent a SSH_MSG_CHANNEL_EOF message @@ -704,7 +705,6 @@ namespace Renci.SshNet.Channels { try { - if (_session.ConnectionInfo.ChannelRequests.TryGetValue(e.Message.RequestName, out var requestInfo)) { // Load request specific data @@ -832,7 +832,7 @@ namespace Renci.SshNet.Channels Close(); var session = _session; - if (session != null) + if (session is not null) { session.ChannelWindowAdjustReceived -= OnChannelWindowAdjust; session.ChannelDataReceived -= OnChannelData; @@ -847,14 +847,14 @@ namespace Renci.SshNet.Channels } var channelClosedWaitHandle = _channelClosedWaitHandle; - if (channelClosedWaitHandle != null) + if (channelClosedWaitHandle is not null) { _channelClosedWaitHandle = null; channelClosedWaitHandle.Dispose(); } var channelServerWindowAdjustWaitHandle = _channelServerWindowAdjustWaitHandle; - if (channelServerWindowAdjustWaitHandle != null) + if (channelServerWindowAdjustWaitHandle is not null) { _channelServerWindowAdjustWaitHandle = null; channelServerWindowAdjustWaitHandle.Dispose(); diff --git a/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs b/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs index 0d67c7af..a8382015 100644 --- a/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs +++ b/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs @@ -17,7 +17,7 @@ namespace Renci.SshNet.Channels private IForwardedPort _forwardedPort; /// - /// Initializes a new instance. + /// Initializes a new instance of the class. /// /// The session. /// The local channel number. @@ -69,17 +69,17 @@ namespace Renci.SshNet.Channels _forwardedPort = forwardedPort; _forwardedPort.Closing += ForwardedPort_Closing; - // Try to connect to the socket + // Try to connect to the socket try { _socket = SocketAbstraction.Connect(remoteEndpoint, ConnectionInfo.Timeout); - // send channel open confirmation message + // Send channel open confirmation message SendMessage(new ChannelOpenConfirmationMessage(RemoteChannelNumber, LocalWindowSize, LocalPacketSize, LocalChannelNumber)); } catch (Exception exp) { - // send channel open failure message + // Send channel open failure message SendMessage(new ChannelOpenFailureMessage(RemoteChannelNumber, exp.ToString(), ChannelOpenFailureMessage.ConnectFailed, "en")); throw; diff --git a/src/Renci.SshNet/Channels/ChannelSession.cs b/src/Renci.SshNet/Channels/ChannelSession.cs index c70e9d31..f222fc64 100644 --- a/src/Renci.SshNet/Channels/ChannelSession.cs +++ b/src/Renci.SshNet/Channels/ChannelSession.cs @@ -394,7 +394,7 @@ namespace Renci.SshNet.Channels /// private void SendChannelOpenMessage() { - // do not allow open to be ChannelOpenMessage to be sent again until we've + // do not allow the ChannelOpenMessage to be sent again until we've // had a response on the previous attempt for the current channel if (Interlocked.CompareExchange(ref _sessionSemaphoreObtained, 1, 0) == 0) { diff --git a/src/Renci.SshNet/Channels/ChannelTypes.cs b/src/Renci.SshNet/Channels/ChannelTypes.cs index 23042023..cd5e0ed0 100644 --- a/src/Renci.SshNet/Channels/ChannelTypes.cs +++ b/src/Renci.SshNet/Channels/ChannelTypes.cs @@ -1,5 +1,4 @@ - -namespace Renci.SshNet.Channels +namespace Renci.SshNet.Channels { /// /// Lists channel types as defined by the protocol. @@ -7,19 +6,22 @@ namespace Renci.SshNet.Channels internal enum ChannelTypes { /// - /// session + /// Session. /// Session, + /// - /// x11 + /// X11. /// X11, + /// - /// forwarded-tcpip + /// Forwarded-tcpip. /// ForwardedTcpip, + /// - /// direct-tcpip + /// Direct-tcpip. /// DirectTcpip } diff --git a/src/Renci.SshNet/Channels/ClientChannel.cs b/src/Renci.SshNet/Channels/ClientChannel.cs index 3a0d05e6..ea4bfe16 100644 --- a/src/Renci.SshNet/Channels/ClientChannel.cs +++ b/src/Renci.SshNet/Channels/ClientChannel.cs @@ -49,7 +49,7 @@ namespace Renci.SshNet.Channels /// /// Send message to open a channel. /// - /// Message to send + /// Message to send. /// The client is not connected. /// The operation timed out. /// The size of the packet exceeds the maximum size defined by the protocol. diff --git a/src/Renci.SshNet/Channels/IChannelSession.cs b/src/Renci.SshNet/Channels/IChannelSession.cs index 4b0bdd20..13c2e230 100644 --- a/src/Renci.SshNet/Channels/IChannelSession.cs +++ b/src/Renci.SshNet/Channels/IChannelSession.cs @@ -25,8 +25,12 @@ namespace Renci.SshNet.Channels /// /// if request was successful; otherwise . /// - bool SendPseudoTerminalRequest(string environmentVariable, uint columns, uint rows, uint width, uint height, - IDictionary terminalModeValues); + bool SendPseudoTerminalRequest(string environmentVariable, + uint columns, + uint rows, + uint width, + uint height, + IDictionary terminalModeValues); /// /// Sends the X11 forwarding request. diff --git a/src/Renci.SshNet/CipherInfo.cs b/src/Renci.SshNet/CipherInfo.cs index 81e5e068..2c9832a1 100644 --- a/src/Renci.SshNet/CipherInfo.cs +++ b/src/Renci.SshNet/CipherInfo.cs @@ -5,7 +5,7 @@ using Renci.SshNet.Security.Cryptography; namespace Renci.SshNet { /// - /// Holds information about key size and cipher to use + /// Holds information about key size and cipher to use. /// public class CipherInfo { diff --git a/src/Renci.SshNet/ClientAuthentication.cs b/src/Renci.SshNet/ClientAuthentication.cs index 1cdfbecb..5de87e53 100644 --- a/src/Renci.SshNet/ClientAuthentication.cs +++ b/src/Renci.SshNet/ClientAuthentication.cs @@ -1,9 +1,14 @@ using System; using System.Collections.Generic; +using System.Globalization; + using Renci.SshNet.Common; namespace Renci.SshNet { + /// + /// Represents a mechanism to authenticate a given client. + /// internal sealed class ClientAuthentication : IClientAuthentication { private readonly int _partialSuccessLimit; @@ -37,11 +42,14 @@ namespace Renci.SshNet } /// - /// Attempts to authentication for a given using the - /// of the specified . + /// Attempts to perform authentication for a given using the + /// of the specified + /// . /// /// A to use for authenticating. /// The for which to perform authentication. + /// or is . + /// Failed to authenticate the client. public void Authenticate(IConnectionInfoInternal connectionInfo, ISession session) { if (connectionInfo is null) @@ -102,8 +110,14 @@ namespace Renci.SshNet var matchingAuthenticationMethods = authenticationState.GetSupportedAuthenticationMethods(allowedAuthenticationMethods); if (matchingAuthenticationMethods.Count == 0) { - authenticationException = new SshAuthenticationException(string.Format("No suitable authentication method found to complete authentication ({0}).", - string.Join(",", allowedAuthenticationMethods))); + authenticationException = new SshAuthenticationException(string.Format(CultureInfo.InvariantCulture, + "No suitable authentication method found to complete authentication ({0}).", +#if NET || NETSTANDARD2_1_OR_GREATER + string.Join(',', allowedAuthenticationMethods))) +#else + string.Join(",", allowedAuthenticationMethods))) +#endif // NET || NETSTANDARD2_1_OR_GREATER + ; return false; } @@ -113,7 +127,7 @@ namespace Renci.SshNet // methods after a partial success if (authenticationState.GetPartialSuccessCount(authenticationMethod) >= _partialSuccessLimit) { - // TODO Get list of all authentication methods that have reached the partial success limit? + /* TODO Get list of all authentication methods that have reached the partial success limit? */ authenticationException = new SshAuthenticationException(string.Format("Reached authentication attempt limit for method ({0}).", authenticationMethod.Name)); @@ -195,7 +209,7 @@ namespace Renci.SshNet { if (_authenticationMethodPartialSuccessRegister.TryGetValue(authenticationMethod, out var partialSuccessCount)) { - _authenticationMethodPartialSuccessRegister[authenticationMethod] = ++partialSuccessCount; + _authenticationMethodPartialSuccessRegister[authenticationMethod] = partialSuccessCount + 1; } else { diff --git a/src/Renci.SshNet/Common/AsyncResult.cs b/src/Renci.SshNet/Common/AsyncResult.cs index 59b71bb4..5f62b1b4 100644 --- a/src/Renci.SshNet/Common/AsyncResult.cs +++ b/src/Renci.SshNet/Common/AsyncResult.cs @@ -155,50 +155,4 @@ namespace Renci.SshNet.Common get { return _completedState != StatePending; } } } - - /// - /// Base class to encapsulates the results of an asynchronous operation that returns result. - /// - /// The type of the result. - public abstract class AsyncResult : AsyncResult - { - // Field set when operation completes - private TResult _result; - - /// - /// Initializes a new instance of the class. - /// - /// The async callback. - /// The state. - protected AsyncResult(AsyncCallback asyncCallback, object state) - : base(asyncCallback, state) - { - } - - /// - /// Marks asynchronous operation as completed. - /// - /// The result. - /// if set to [completed synchronously]. - public void SetAsCompleted(TResult result, bool completedSynchronously) - { - // Save the asynchronous operation's result - _result = result; - - // Tell the base class that the operation completed successfully (no exception) - SetAsCompleted(exception: null, completedSynchronously); - } - - /// - /// Waits until the asynchronous operation completes, and then returns the value generated by the asynchronous operation. - /// - /// - /// The invocation result. - /// - public new TResult EndInvoke() - { - base.EndInvoke(); // Wait until operation has completed - return _result; // Return the result (if above didn't throw) - } - } } diff --git a/src/Renci.SshNet/Common/AsyncResult{TResult}.cs b/src/Renci.SshNet/Common/AsyncResult{TResult}.cs new file mode 100644 index 00000000..b5ab5412 --- /dev/null +++ b/src/Renci.SshNet/Common/AsyncResult{TResult}.cs @@ -0,0 +1,50 @@ +using System; + +namespace Renci.SshNet.Common +{ + /// + /// Base class to encapsulates the results of an asynchronous operation that returns result. + /// + /// The type of the result. + public abstract class AsyncResult : AsyncResult + { + // Field set when operation completes + private TResult _result; + + /// + /// Initializes a new instance of the class. + /// + /// The async callback. + /// The state. + protected AsyncResult(AsyncCallback asyncCallback, object state) + : base(asyncCallback, state) + { + } + + /// + /// Marks asynchronous operation as completed. + /// + /// The result. + /// if set to [completed synchronously]. + public void SetAsCompleted(TResult result, bool completedSynchronously) + { + // Save the asynchronous operation's result + _result = result; + + // Tell the base class that the operation completed successfully (no exception) + SetAsCompleted(exception: null, completedSynchronously); + } + + /// + /// Waits until the asynchronous operation completes, and then returns the value generated by the asynchronous operation. + /// + /// + /// The invocation result. + /// + public new TResult EndInvoke() + { + base.EndInvoke(); // Wait until operation has completed + return _result; // Return the result (if above didn't throw) + } + } +} diff --git a/src/Renci.SshNet/Common/BigInteger.cs b/src/Renci.SshNet/Common/BigInteger.cs index 862af076..53a659da 100644 --- a/src/Renci.SshNet/Common/BigInteger.cs +++ b/src/Renci.SshNet/Common/BigInteger.cs @@ -1,4 +1,6 @@ -// +#pragma warning disable SA1028 // Code should not contain trailing whitespace +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line +// // System.Numerics.BigInteger // // Authors: @@ -44,6 +46,8 @@ * * * ***************************************************************************/ +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line +#pragma warning restore SA1028 // Code should not contain trailing whitespace using System; using System.Collections.Generic; @@ -106,7 +110,7 @@ namespace Renci.SshNet.Common var msbBitCount = BitScanBackward(_data[msbIndex]) + 1; - return msbIndex * 4 * 8 + msbBitCount + ((_sign > 0) ? 0 : 1); + return (msbIndex * 4 * 8) + msbBitCount + ((_sign > 0) ? 0 : 1); } } @@ -145,8 +149,8 @@ namespace Renci.SshNet.Common p1 += (b / a) * p0; b %= a; - } + return 0; } @@ -210,7 +214,9 @@ namespace Renci.SshNet.Common else { _sign = -1; +#pragma warning disable SA1021 // Negative signs should be spaced correctly _data = new[] { (uint) -value }; +#pragma warning restore SA1021 // Negative signs should be spaced correctly } } @@ -419,7 +425,9 @@ namespace Renci.SshNet.Common _sign = 1; } +#pragma warning disable CA1508 // Avoid dead conditional code | this is the following bug in the analyzer rule: https://github.com/dotnet/roslyn-analyzers/issues/6991 if (_sign == 1) +#pragma warning restore CA1508 // Avoid dead conditional code { while (value[len - 1] == 0) { @@ -514,8 +522,9 @@ namespace Renci.SshNet.Common if (borrow != 0) { - // FIXME I believe this can't happen, can someone write a test for it? +#pragma warning disable CA2201 // Do not raise reserved exception types throw new Exception("non zero final carry"); +#pragma warning restore CA2201 // Do not raise reserved exception types } } } @@ -535,14 +544,14 @@ namespace Renci.SshNet.Common var i1 = (uint)v[0] | ((uint)v[1] << 8) | ((uint)v[2] << 16) | ((uint)v[3] << 24); var i2 = (uint)v[4] | ((uint)v[5] << 8) | ((uint)(v[6] & 0xF) << 16); - return ((ulong) i1 | ((ulong) i2 << 32)); + return (ulong) i1 | ((ulong) i2 << 32); } /// /// Gets a value indicating whether the value of the current object is an even number. /// /// - /// if the value of the BigInteger object is an even number; otherwise, . + /// if the value of the object is an even number; otherwise, . /// public readonly bool IsEven { @@ -768,7 +777,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator int(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (value._data is null) { @@ -813,7 +824,9 @@ namespace Renci.SshNet.Common /// An object that contains the value of the parameter. /// [CLSCompliant(false)] +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator uint(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (value._data is null) { @@ -835,7 +848,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator short(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { var val = (int) value; if (val is < short.MinValue or > short.MaxValue) @@ -854,7 +869,9 @@ namespace Renci.SshNet.Common /// An object that contains the value of the parameter. /// [CLSCompliant(false)] +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator ushort(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { var val = (uint) value; if (val > ushort.MaxValue) @@ -872,7 +889,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator byte(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { var val = (uint) value; if (val > byte.MaxValue) @@ -891,7 +910,9 @@ namespace Renci.SshNet.Common /// An object that contains the value of the parameter. /// [CLSCompliant(false)] +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator sbyte(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { var val = (int) value; if (val is < sbyte.MinValue or > sbyte.MaxValue) @@ -909,7 +930,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator long(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (value._data is null) { @@ -973,7 +996,9 @@ namespace Renci.SshNet.Common /// An object that contains the value of the parameter. /// [CLSCompliant(false)] +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator ulong(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (value._data is null) { @@ -1002,7 +1027,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator double(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (value._data is null) { @@ -1041,7 +1068,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator float(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return (float) (double) value; } @@ -1053,7 +1082,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator decimal(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (value._data is null) { @@ -1092,7 +1123,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static implicit operator BigInteger(int value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } @@ -1105,7 +1138,9 @@ namespace Renci.SshNet.Common /// An object that contains the value of the parameter. /// [CLSCompliant(false)] +#pragma warning disable CA2225 // Operator overloads have named alternates public static implicit operator BigInteger(uint value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } @@ -1117,7 +1152,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static implicit operator BigInteger(short value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } @@ -1130,7 +1167,9 @@ namespace Renci.SshNet.Common /// An object that contains the value of the parameter. /// [CLSCompliant(false)] +#pragma warning disable CA2225 // Operator overloads have named alternates public static implicit operator BigInteger(ushort value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } @@ -1142,20 +1181,24 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static implicit operator BigInteger(byte value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } /// - /// + /// Defines an implicit conversion of a signed byte to a value. /// /// The value to convert to a . /// /// An object that contains the value of the parameter. /// [CLSCompliant(false)] +#pragma warning disable CA2225 // Operator overloads have named alternates public static implicit operator BigInteger(sbyte value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } @@ -1167,7 +1210,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static implicit operator BigInteger(long value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } @@ -1180,7 +1225,9 @@ namespace Renci.SshNet.Common /// An object that contains the value of the parameter. /// [CLSCompliant(false)] +#pragma warning disable CA2225 // Operator overloads have named alternates public static implicit operator BigInteger(ulong value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } @@ -1192,7 +1239,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator BigInteger(double value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } @@ -1204,7 +1253,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator BigInteger(float value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } @@ -1216,7 +1267,9 @@ namespace Renci.SshNet.Common /// /// An object that contains the value of the parameter. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static explicit operator BigInteger(decimal value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return new BigInteger(value); } @@ -1255,7 +1308,7 @@ namespace Renci.SshNet.Common if (r > 0) { - //left > right + // left > right return new BigInteger(left._sign, CoreSub(left._data, right._data)); } @@ -1279,7 +1332,9 @@ namespace Renci.SshNet.Common if (left._sign == 0) { +#pragma warning disable SA1021 // Negative signs should be spaced correctly return new BigInteger((short) -right._sign, right._data); +#pragma warning restore SA1021 // Negative signs should be spaced correctly } if (left._sign == right._sign) @@ -1351,7 +1406,7 @@ namespace Renci.SshNet.Common ulong carry = 0; for (var j = 0; j < b.Length; ++j) { - carry = carry + ((ulong) ai) * b[j] + res[k]; + carry = carry + (((ulong) ai) * b[j]) + res[k]; res[k++] = (uint)carry; carry >>= 32; } @@ -1475,7 +1530,9 @@ namespace Renci.SshNet.Common return value; } +#pragma warning disable SA1021 // Negative signs should be spaced correctly return new BigInteger((short) -value._sign, value._data); +#pragma warning restore SA1021 // Negative signs should be spaced correctly } /// @@ -1488,7 +1545,9 @@ namespace Renci.SshNet.Common /// /// The sign of the operand is unchanged. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static BigInteger operator +(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { return value; } @@ -1500,7 +1559,9 @@ namespace Renci.SshNet.Common /// /// The value of the parameter incremented by 1. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static BigInteger operator ++(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (value._data is null) { @@ -1534,7 +1595,9 @@ namespace Renci.SshNet.Common /// /// The value of the parameter decremented by 1. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static BigInteger operator --(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (value._data is null) { @@ -1569,7 +1632,9 @@ namespace Renci.SshNet.Common /// /// The result of the bitwise And operation. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static BigInteger operator &(BigInteger left, BigInteger right) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (left._sign == 0) { @@ -1659,7 +1724,9 @@ namespace Renci.SshNet.Common /// /// The result of the bitwise Or operation. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static BigInteger operator |(BigInteger left, BigInteger right) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (left._sign == 0) { @@ -1749,7 +1816,9 @@ namespace Renci.SshNet.Common /// /// The result of the bitwise Or operation. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static BigInteger operator ^(BigInteger left, BigInteger right) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (left._sign == 0) { @@ -1838,7 +1907,9 @@ namespace Renci.SshNet.Common /// /// The bitwise one's complement of . /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static BigInteger operator ~(BigInteger value) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (value._data is null) { @@ -1895,9 +1966,14 @@ namespace Renci.SshNet.Common return new BigInteger(negRes ? (short)-1 : (short)1, result); } - //returns the 0-based index of the most significant set bit - //returns 0 if no bit is set, so extra care when using it - static int BitScanBackward(uint word) + /// + /// Returns the zero-based index of the most significant set bit. + /// + /// The value to scan. + /// + /// The zero-based index of the most significant set bit, or zero if no bit is set. + /// + private static int BitScanBackward(uint word) { for (var i = 31; i >= 0; --i) { @@ -1919,7 +1995,9 @@ namespace Renci.SshNet.Common /// /// A value that has been shifted to the left by the specified number of bits. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static BigInteger operator <<(BigInteger value, int shift) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (shift == 0 || value._data is null) { @@ -1976,7 +2054,9 @@ namespace Renci.SshNet.Common /// /// A value that has been shifted to the right by the specified number of bits. /// +#pragma warning disable CA2225 // Operator overloads have named alternates public static BigInteger operator >>(BigInteger value, int shift) +#pragma warning restore CA2225 // Operator overloads have named alternates { if (shift == 0 || value._sign == 0) { @@ -2092,7 +2172,6 @@ namespace Renci.SshNet.Common return left.CompareTo(right) < 0; } - /// /// Returns a value that indicates whether a 64-bit signed integer is less than a value. /// @@ -2672,7 +2751,11 @@ namespace Renci.SshNet.Common return additional + baseStr; } +#if NET + return string.Concat("-", additional, baseStr.AsSpan(1)); +#else return "-" + additional + baseStr.Substring(1); +#endif // NET } return baseStr; @@ -2689,9 +2772,9 @@ namespace Renci.SshNet.Common for (var i = 0; i < v.Length; ++i) { var word = v[i]; - carry = (ulong)~word + carry; - word = (uint)carry; - carry = (uint)(carry >> 32); + carry = (ulong) ~word + carry; + word = (uint) carry; + carry = (uint) (carry >> 32); res[i] = word; } @@ -2713,7 +2796,7 @@ namespace Renci.SshNet.Common if (characterSet.Length < radix) { - throw new ArgumentException("charSet length less than radix", "characterSet"); + throw new ArgumentException("charSet length less than radix", nameof(radix)); } if (radix == 1) @@ -2731,7 +2814,7 @@ namespace Renci.SshNet.Common return _sign == 1 ? "1" : "-1"; } - var digits = new List(1 + _data.Length * 3 / 10); + var digits = new List(1 + ((_data.Length * 3) / 10)); BigInteger a; if (_sign == 1) @@ -2910,7 +2993,9 @@ namespace Renci.SshNet.Common return true; } +#pragma warning disable S4136 // Method overloads should be grouped together private static bool Parse(string value, NumberStyles style, IFormatProvider fp, bool tryParse, out BigInteger result, out Exception exc) +#pragma warning restore S4136 // Method overloads should be grouped together { result = Zero; exc = null; @@ -2949,16 +3034,16 @@ namespace Renci.SshNet.Common return false; } - var allowCurrencySymbol = (style & NumberStyles.AllowCurrencySymbol) != 0; - var allowHexSpecifier = (style & NumberStyles.AllowHexSpecifier) != 0; - var allowThousands = (style & NumberStyles.AllowThousands) != 0; - var allowDecimalPoint = (style & NumberStyles.AllowDecimalPoint) != 0; - var allowParentheses = (style & NumberStyles.AllowParentheses) != 0; - var allowTrailingSign = (style & NumberStyles.AllowTrailingSign) != 0; - var allowLeadingSign = (style & NumberStyles.AllowLeadingSign) != 0; - var allowTrailingWhite = (style & NumberStyles.AllowTrailingWhite) != 0; - var allowLeadingWhite = (style & NumberStyles.AllowLeadingWhite) != 0; - var allowExponent = (style & NumberStyles.AllowExponent) != 0; + var allowCurrencySymbol = (style & NumberStyles.AllowCurrencySymbol) == NumberStyles.AllowCurrencySymbol; + var allowHexSpecifier = (style & NumberStyles.AllowHexSpecifier) == NumberStyles.AllowHexSpecifier; + var allowThousands = (style & NumberStyles.AllowThousands) == NumberStyles.AllowThousands; + var allowDecimalPoint = (style & NumberStyles.AllowDecimalPoint) == NumberStyles.AllowDecimalPoint; + var allowParentheses = (style & NumberStyles.AllowParentheses) == NumberStyles.AllowParentheses; + var allowTrailingSign = (style & NumberStyles.AllowTrailingSign) == NumberStyles.AllowTrailingSign; + var allowLeadingSign = (style & NumberStyles.AllowLeadingSign) == NumberStyles.AllowLeadingSign; + var allowTrailingWhite = (style & NumberStyles.AllowTrailingWhite) == NumberStyles.AllowTrailingWhite; + var allowLeadingWhite = (style & NumberStyles.AllowLeadingWhite) == NumberStyles.AllowLeadingWhite; + var allowExponent = (style & NumberStyles.AllowExponent) == NumberStyles.AllowExponent; var pos = 0; @@ -3223,7 +3308,11 @@ namespace Renci.SshNet.Common { if (!tryParse) { - exc = new OverflowException("Value too large or too small. exp=" + exponent + " rem = " + remainder + " pow = " + Pow(10, -exponent)); + exc = new OverflowException(string.Format(CultureInfo.InvariantCulture, + "Value too large or too small. exp= {0} rem = {1} pow = {2}", + exponent, + remainder, + Pow(10, -exponent))); } return false; @@ -3252,20 +3341,20 @@ namespace Renci.SshNet.Common private static bool CheckStyle(NumberStyles style, bool tryParse, ref Exception exc) { - if ((style & NumberStyles.AllowHexSpecifier) != 0) + if ((style & NumberStyles.AllowHexSpecifier) == NumberStyles.AllowHexSpecifier) { var ne = style ^ NumberStyles.AllowHexSpecifier; - if ((ne & NumberStyles.AllowLeadingWhite) != 0) + if ((ne & NumberStyles.AllowLeadingWhite) == NumberStyles.AllowLeadingWhite) { ne ^= NumberStyles.AllowLeadingWhite; } - if ((ne & NumberStyles.AllowTrailingWhite) != 0) + if ((ne & NumberStyles.AllowTrailingWhite) == NumberStyles.AllowTrailingWhite) { ne ^= NumberStyles.AllowTrailingWhite; } - if (ne != 0) + if (ne != NumberStyles.None) { if (!tryParse) { @@ -3424,7 +3513,7 @@ namespace Renci.SshNet.Common return char.IsDigit(e); } - private static Exception GetFormatException() + private static FormatException GetFormatException() { return new FormatException("Input string was not in the correct format"); } @@ -3545,7 +3634,9 @@ namespace Renci.SshNet.Common { result = val; } +#pragma warning disable CA1508 // Avoid dead conditional code | this is the following bug in the analyzer rule: https://github.com/dotnet/roslyn-analyzers/issues/6991 else if (sign == -1) +#pragma warning restore CA1508 // Avoid dead conditional code { result = new BigInteger(-1, val._data); } @@ -3836,7 +3927,6 @@ namespace Renci.SshNet.Common g = x; x = y % x; y = g; - } if (x.IsZero) @@ -3926,7 +4016,7 @@ namespace Renci.SshNet.Common { if ((value._data[length] & (1 << curBit)) != 0) { - bitCount = curBit + length * 32; + bitCount = curBit + (length * 32); break; } } @@ -4472,7 +4562,7 @@ namespace Renci.SshNet.Common if (carry == 0) { var ex = FirstNonFfByte(word); - var needExtra = (word & (1 << (ex * 8 - 1))) == 0; + var needExtra = (word & (1 << ((ex * 8) - 1))) == 0; var to = ex + (needExtra ? 1 : 0); if (to != extra) @@ -4488,7 +4578,9 @@ namespace Renci.SshNet.Common if (needExtra) { +#pragma warning disable S1854 // Unused assignments should be removed res[j++] = 0xFF; +#pragma warning restore S1854 // Unused assignments should be removed } } else @@ -4498,7 +4590,9 @@ namespace Renci.SshNet.Common res[j++] = (byte)(word >> 8); res[j++] = (byte)(word >> 16); res[j++] = (byte)(word >> 24); +#pragma warning disable S1854 // Unused assignments should be removed res[j++] = 0xFF; +#pragma warning restore S1854 // Unused assignments should be removed } } @@ -4807,14 +4901,14 @@ namespace Renci.SshNet.Common { int i; - var rr = Base * un[j + n] + un[j + n - 1]; + var rr = (Base * un[j + n]) + un[j + n - 1]; var qq = rr / vn[n - 1]; rr -= qq * vn[n - 1]; - for (;;) + for (; ; ) { // Estimate too big ? - if ((qq >= Base) || (qq * vn[n - 2] > (rr * Base + un[j + n - 2]))) + if ((qq >= Base) || (qq * vn[n - 2] > ((rr * Base) + un[j + n - 2]))) { qq--; rr += (ulong)vn[n - 1]; @@ -4827,7 +4921,6 @@ namespace Renci.SshNet.Common break; } - // Multiply and subtract long b = 0; long t; diff --git a/src/Renci.SshNet/Common/ChannelDataEventArgs.cs b/src/Renci.SshNet/Common/ChannelDataEventArgs.cs index 4891fe24..dbc20b5b 100644 --- a/src/Renci.SshNet/Common/ChannelDataEventArgs.cs +++ b/src/Renci.SshNet/Common/ChannelDataEventArgs.cs @@ -1,4 +1,6 @@ -namespace Renci.SshNet.Common +using System; + +namespace Renci.SshNet.Common { /// /// Provides data for event. @@ -10,9 +12,15 @@ /// /// Channel number. /// Channel data. + /// is . public ChannelDataEventArgs(uint channelNumber, byte[] data) : base(channelNumber) { + if (data is null) + { + throw new ArgumentNullException(nameof(data)); + } + Data = data; } diff --git a/src/Renci.SshNet/Common/ChannelRequestEventArgs.cs b/src/Renci.SshNet/Common/ChannelRequestEventArgs.cs index 7747b762..95548194 100644 --- a/src/Renci.SshNet/Common/ChannelRequestEventArgs.cs +++ b/src/Renci.SshNet/Common/ChannelRequestEventArgs.cs @@ -13,8 +13,14 @@ namespace Renci.SshNet.Common /// Initializes a new instance of the class. /// /// Request information. + /// is . public ChannelRequestEventArgs(RequestInfo info) { + if (info is null) + { + throw new ArgumentNullException(nameof(info)); + } + Info = info; } diff --git a/src/Renci.SshNet/Common/DerData.cs b/src/Renci.SshNet/Common/DerData.cs index 29361ace..ba0a678d 100644 --- a/src/Renci.SshNet/Common/DerData.cs +++ b/src/Renci.SshNet/Common/DerData.cs @@ -48,7 +48,7 @@ namespace Renci.SshNet.Common /// Initializes a new instance of the class. /// /// DER encoded data. - /// its a construct + /// its a construct. public DerData(byte[] data, bool construct = false) { _data = new List(data); diff --git a/src/Renci.SshNet/Common/Extensions.cs b/src/Renci.SshNet/Common/Extensions.cs index 03f05be8..f364dc9f 100644 --- a/src/Renci.SshNet/Common/Extensions.cs +++ b/src/Renci.SshNet/Common/Extensions.cs @@ -50,7 +50,7 @@ namespace Renci.SshNet.Common } /// - /// Initializes a new instance of the structure using the SSH BigNum2 Format + /// Initializes a new instance of the structure using the SSH BigNum2 Format. /// public static BigInteger ToBigInteger2(this byte[] data) { @@ -60,6 +60,7 @@ namespace Renci.SshNet.Common Buffer.BlockCopy(data, 0, buf, 1, data.Length); data = buf; } + return data.ToBigInteger(); } diff --git a/src/Renci.SshNet/Common/HostKeyEventArgs.cs b/src/Renci.SshNet/Common/HostKeyEventArgs.cs index 79e6aac8..acd38965 100644 --- a/src/Renci.SshNet/Common/HostKeyEventArgs.cs +++ b/src/Renci.SshNet/Common/HostKeyEventArgs.cs @@ -30,7 +30,7 @@ namespace Renci.SshNet.Common /// /// Gets the host key name. /// - public string HostKeyName{ get; private set; } + public string HostKeyName { get; private set; } /// /// Gets the MD5 fingerprint. @@ -50,7 +50,7 @@ namespace Renci.SshNet.Common /// Gets the SHA256 fingerprint of the host key in the same format as the ssh command, /// i.e. non-padded base64, but without the SHA256: prefix. /// - /// ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og + /// ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og. /// /// Base64 encoded SHA256 fingerprint with padding (equals sign) removed. /// @@ -66,7 +66,7 @@ namespace Renci.SshNet.Common /// Gets the MD5 fingerprint of the host key in the same format as the ssh command, /// i.e. hexadecimal bytes separated by colons, but without the MD5: prefix. /// - /// 97:70:33:82:fd:29:3a:73:39:af:6a:07:ad:f8:80:49 + /// 97:70:33:82:fd:29:3a:73:39:af:6a:07:ad:f8:80:49. public string FingerPrintMD5 { get @@ -87,27 +87,43 @@ namespace Renci.SshNet.Common /// Initializes a new instance of the class. /// /// The host. + /// is . public HostKeyEventArgs(KeyHostAlgorithm host) { + if (host is null) + { + throw new ArgumentNullException(nameof(host)); + } + CanTrust = true; HostKey = host.Data; HostKeyName = host.Name; KeyLength = host.Key.KeyLength; - + _lazyFingerPrint = new Lazy(() => - { - using var md5 = CryptoAbstraction.CreateMD5(); - return md5.ComputeHash(HostKey); - }); + { + using var md5 = CryptoAbstraction.CreateMD5(); + return md5.ComputeHash(HostKey); + }); _lazyFingerPrintSHA256 = new Lazy(() => - { - using var sha256 = CryptoAbstraction.CreateSHA256(); - return Convert.ToBase64String(sha256.ComputeHash(HostKey)).Replace("=", ""); - }); + { + using var sha256 = CryptoAbstraction.CreateSHA256(); - _lazyFingerPrintMD5 = new Lazy(() => - BitConverter.ToString(FingerPrint).Replace("-", ":").ToLowerInvariant()); + return Convert.ToBase64String(sha256.ComputeHash(HostKey)) +#if NET || NETSTANDARD2_1_OR_GREATER + .Replace("=", string.Empty, StringComparison.Ordinal); +#else + .Replace("=", string.Empty); +#endif // NET || NETSTANDARD2_1_OR_GREATER + }); + + _lazyFingerPrintMD5 = new Lazy(() => + { +#pragma warning disable CA1308 // Normalize strings to uppercase + return BitConverter.ToString(FingerPrint).Replace('-', ':').ToLowerInvariant(); +#pragma warning restore CA1308 // Normalize strings to uppercase + }); } } } diff --git a/src/Renci.SshNet/Common/NetConfServerException.cs b/src/Renci.SshNet/Common/NetConfServerException.cs index 0d807f0c..e8614bcb 100644 --- a/src/Renci.SshNet/Common/NetConfServerException.cs +++ b/src/Renci.SshNet/Common/NetConfServerException.cs @@ -41,7 +41,7 @@ namespace Renci.SshNet.Common #if FEATURE_BINARY_SERIALIZATION /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. diff --git a/src/Renci.SshNet/Common/ObjectIdentifier.cs b/src/Renci.SshNet/Common/ObjectIdentifier.cs index a02a4090..5aa310a5 100644 --- a/src/Renci.SshNet/Common/ObjectIdentifier.cs +++ b/src/Renci.SshNet/Common/ObjectIdentifier.cs @@ -7,7 +7,9 @@ namespace Renci.SshNet.Common /// /// Describes object identifier for DER encoding. /// +#pragma warning disable CA1815 // Override equals and operator equals on value types public struct ObjectIdentifier +#pragma warning restore CA1815 // Override equals and operator equals on value types { /// /// Gets the object identifier. diff --git a/src/Renci.SshNet/Common/PipeStream.cs b/src/Renci.SshNet/Common/PipeStream.cs index 887766f3..da7b89cb 100644 --- a/src/Renci.SshNet/Common/PipeStream.cs +++ b/src/Renci.SshNet/Common/PipeStream.cs @@ -10,8 +10,6 @@ namespace Renci.SshNet.Common /// PipeStream is a thread-safe read/write data stream for use between two threads in a /// single-producer/single-consumer type problem. /// - /// 2006/10/13 1.0 - /// Update on 2008/10/9 1.1 - uses Monitor instead of Manual Reset events for more elegant synchronicity. /// /// Copyright (c) 2006 James Kolpack (james dot kolpack at google mail) /// @@ -46,11 +44,6 @@ namespace Renci.SshNet.Common /// private bool _isFlushed; - /// - /// Maximum number of bytes to store in the buffer. - /// - private long _maxBufferLength = 200 * 1024 * 1024; - /// /// Setting this to true will cause Read() to block if it appears /// that it will run out of data. @@ -66,11 +59,7 @@ namespace Renci.SshNet.Common /// Gets or sets the maximum number of bytes to store in the buffer. /// /// The length of the max buffer. - public long MaxBufferLength - { - get { return _maxBufferLength; } - set { _maxBufferLength = value; } - } + public long MaxBufferLength { get; set; } = 200 * 1024 * 1024; /// /// Gets or sets a value indicating whether to block last read method before the buffer is empty. @@ -88,19 +77,27 @@ namespace Renci.SshNet.Common { get { +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_isDisposed, this); +#else if (_isDisposed) { throw CreateObjectDisposedException(); } +#endif // NET7_0_OR_GREATER return _canBlockLastRead; } set { +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_isDisposed, this); +#else if (_isDisposed) { throw CreateObjectDisposedException(); } +#endif // NET7_0_OR_GREATER _canBlockLastRead = value; @@ -126,10 +123,14 @@ namespace Renci.SshNet.Common /// public override void Flush() { +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_isDisposed, this); +#else if (_isDisposed) { throw CreateObjectDisposedException(); } +#endif // NET7_0_OR_GREATER _isFlushed = true; lock (_buffer) @@ -200,15 +201,19 @@ namespace Renci.SshNet.Common throw new ArgumentOutOfRangeException(nameof(offset), "offset or count is negative."); } - if (BlockLastReadBuffer && count >= _maxBufferLength) + if (BlockLastReadBuffer && count >= MaxBufferLength) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "count({0}) > mMaxBufferLength({1})", count, _maxBufferLength)); + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "count({0}) > mMaxBufferLength({1})", count, MaxBufferLength)); } +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_isDisposed, this); +#else if (_isDisposed) { throw CreateObjectDisposedException(); } +#endif // NET7_0_OR_GREATER if (count == 0) { @@ -284,10 +289,14 @@ namespace Renci.SshNet.Common throw new ArgumentOutOfRangeException(nameof(offset), "offset or count is negative."); } +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_isDisposed, this); +#else if (_isDisposed) { throw CreateObjectDisposedException(); } +#endif // NET7_0_OR_GREATER if (count == 0) { @@ -297,7 +306,7 @@ namespace Renci.SshNet.Common lock (_buffer) { // wait until the buffer isn't full - while (Length >= _maxBufferLength) + while (Length >= MaxBufferLength) { _ = Monitor.Wait(_buffer); } @@ -380,10 +389,14 @@ namespace Renci.SshNet.Common { get { +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_isDisposed, this); +#else if (_isDisposed) { throw CreateObjectDisposedException(); } +#endif // NET7_0_OR_GREATER return _buffer.Count; } @@ -402,9 +415,11 @@ namespace Renci.SshNet.Common set { throw new NotSupportedException(); } } +#if !NET7_0_OR_GREATER private ObjectDisposedException CreateObjectDisposedException() { return new ObjectDisposedException(GetType().FullName); } +#endif // !NET7_0_OR_GREATER } } diff --git a/src/Renci.SshNet/Common/SemaphoreLight.cs b/src/Renci.SshNet/Common/SemaphoreLight.cs index d506d44e..106b1018 100644 --- a/src/Renci.SshNet/Common/SemaphoreLight.cs +++ b/src/Renci.SshNet/Common/SemaphoreLight.cs @@ -6,7 +6,7 @@ namespace Renci.SshNet.Common /// /// Light implementation of SemaphoreSlim. /// - public class SemaphoreLight : IDisposable + public sealed class SemaphoreLight : IDisposable { private readonly object _lock = new object(); private ManualResetEvent _waitHandle; @@ -228,7 +228,7 @@ namespace Renci.SshNet.Common /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. - protected void Dispose(bool disposing) + private void Dispose(bool disposing) { if (disposing) { diff --git a/src/Renci.SshNet/Common/SshConnectionException.cs b/src/Renci.SshNet/Common/SshConnectionException.cs index 75c7227a..a5c227b8 100644 --- a/src/Renci.SshNet/Common/SshConnectionException.cs +++ b/src/Renci.SshNet/Common/SshConnectionException.cs @@ -47,6 +47,17 @@ namespace Renci.SshNet.Common DisconnectReason = disconnectReasonCode; } + /// + /// Initializes a new instance of the class. + /// + /// The message. + /// The inner. + public SshConnectionException(string message, Exception inner) + : base(message, inner) + { + DisconnectReason = DisconnectReason.None; + } + /// /// Initializes a new instance of the class. /// diff --git a/src/Renci.SshNet/Common/SshData.cs b/src/Renci.SshNet/Common/SshData.cs index 5dd9fa9e..9cbf4fd9 100644 --- a/src/Renci.SshNet/Common/SshData.cs +++ b/src/Renci.SshNet/Common/SshData.cs @@ -7,7 +7,9 @@ namespace Renci.SshNet.Common /// /// Base ssh data serialization type. /// +#pragma warning disable CA1001 // Types that own disposable fields should be disposable public abstract class SshData +#pragma warning restore CA1001 // Types that own disposable fields should be disposable { internal const int DefaultCapacity = 64; @@ -63,9 +65,12 @@ namespace Renci.SshNet.Common { var messageLength = BufferCapacity; var capacity = messageLength != -1 ? messageLength : DefaultCapacity; - var dataStream = new SshDataStream(capacity); - WriteBytes(dataStream); - return dataStream.ToArray(); + + using (var dataStream = new SshDataStream(capacity)) + { + WriteBytes(dataStream); + return dataStream.ToArray(); + } } /// @@ -129,7 +134,9 @@ namespace Renci.SshNet.Common /// /// Reads all data left in internal buffer at current position. /// - /// An array of bytes containing the remaining data in the internal buffer. + /// + /// An array of bytes containing the remaining data in the internal buffer. + /// protected byte[] ReadBytes() { var bytesLength = (int) (_stream.Length - _stream.Position); @@ -142,16 +149,12 @@ namespace Renci.SshNet.Common /// Reads next specified number of bytes data type from internal buffer. /// /// Number of bytes to read. - /// An array of bytes that was read from the internal buffer. - /// is greater than the internal buffer size. + /// + /// An array of bytes that was read from the internal buffer. + /// + /// is greater than the number of bytes available to be read. protected byte[] ReadBytes(int length) { - /* - * Note that this also prevents allocating non-relevant lengths, such as if length is greater than _data.Count but less than int.MaxValue. - * For the nerds, the condition translates to: if (length > data.Count && length < int.MaxValue) - * Which probably would cause all sorts of exception, most notably OutOfMemoryException. - */ - var data = new byte[length]; var bytesRead = _stream.Read(data, 0, length); @@ -166,7 +169,10 @@ namespace Renci.SshNet.Common /// /// Reads next byte data type from internal buffer. /// - /// Byte read. + /// + /// The read. + /// + /// Attempt to read past the end of the stream. protected byte ReadByte() { var byteRead = _stream.ReadByte(); @@ -184,6 +190,7 @@ namespace Renci.SshNet.Common /// /// The that was read. /// + /// Attempt to read past the end of the stream. protected bool ReadBoolean() { return ReadByte() != 0; @@ -195,6 +202,7 @@ namespace Renci.SshNet.Common /// /// The that was read. /// + /// Attempt to read past the end of the stream. protected ushort ReadUInt16() { return Pack.BigEndianToUInt16(ReadBytes(2)); @@ -206,6 +214,7 @@ namespace Renci.SshNet.Common /// /// The that was read. /// + /// Attempt to read past the end of the stream. protected uint ReadUInt32() { return Pack.BigEndianToUInt32(ReadBytes(4)); @@ -217,6 +226,7 @@ namespace Renci.SshNet.Common /// /// The that was read. /// + /// Attempt to read past the end of the stream. protected ulong ReadUInt64() { return Pack.BigEndianToUInt64(ReadBytes(8)); @@ -260,8 +270,10 @@ namespace Renci.SshNet.Common /// /// Reads next extension-pair data type from internal buffer. /// - /// Extensions pair dictionary. - protected IDictionary ReadExtensionPair() + /// + /// Extensions pair dictionary. + /// + protected Dictionary ReadExtensionPair() { var result = new Dictionary(); @@ -373,7 +385,11 @@ namespace Renci.SshNet.Common /// name-list data to write. protected void Write(string[] data) { +#if NET || NETSTANDARD2_1_OR_GREATER + Write(string.Join(',', data), Ascii); +#else Write(string.Join(",", data), Ascii); +#endif // NET || NETSTANDARD2_1_OR_GREATER } /// diff --git a/src/Renci.SshNet/Common/SshDataStream.cs b/src/Renci.SshNet/Common/SshDataStream.cs index 3a15d743..6bad5f21 100644 --- a/src/Renci.SshNet/Common/SshDataStream.cs +++ b/src/Renci.SshNet/Common/SshDataStream.cs @@ -101,6 +101,24 @@ namespace Renci.SshNet.Common Write(data, 0, data.Length); } + /// + /// Writes string data to the SSH data stream using the specified encoding. + /// + /// The string data to write. + /// The character encoding to use. + /// is . + /// is . + public void Write(string s, Encoding encoding) + { + if (encoding is null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + var bytes = encoding.GetBytes(s); + WriteBinary(bytes, 0, bytes.Length); + } + /// /// Reads a byte array from the SSH data stream. /// @@ -149,24 +167,6 @@ namespace Renci.SshNet.Common Write(buffer, offset, count); } - /// - /// Writes string data to the SSH data stream using the specified encoding. - /// - /// The string data to write. - /// The character encoding to use. - /// is . - /// is . - public void Write(string s, Encoding encoding) - { - if (encoding is null) - { - throw new ArgumentNullException(nameof(encoding)); - } - - var bytes = encoding.GetBytes(s); - WriteBinary(bytes, 0, bytes.Length); - } - /// /// Reads a from the SSH datastream. /// diff --git a/src/Renci.SshNet/Common/TerminalModes.cs b/src/Renci.SshNet/Common/TerminalModes.cs index 8737872b..6b813837 100644 --- a/src/Renci.SshNet/Common/TerminalModes.cs +++ b/src/Renci.SshNet/Common/TerminalModes.cs @@ -1,10 +1,13 @@ namespace Renci.SshNet.Common { /// - /// Specifies the initial assignments of the opcode values that are used in the 'encoded terminal modes' valu + /// Specifies the initial assignments of the opcode values that are used in the 'encoded terminal modes' value. /// +#pragma warning disable CA1028 // Enum Storage should be Int32 public enum TerminalModes : byte +#pragma warning restore CA1028 // Enum Storage should be Int32 { +#pragma warning disable CA1707 // Identifiers should not contain underscores /// /// Indicates end of options. /// @@ -289,5 +292,6 @@ /// Specifies the output baud rate in bits per second. /// TTY_OP_OSPEED = 129, +#pragma warning restore CA1707 // Identifiers should not contain underscores } } diff --git a/src/Renci.SshNet/Compression/CompressionMode.cs b/src/Renci.SshNet/Compression/CompressionMode.cs index 1577e0bc..b0b6f6b9 100644 --- a/src/Renci.SshNet/Compression/CompressionMode.cs +++ b/src/Renci.SshNet/Compression/CompressionMode.cs @@ -1,7 +1,7 @@ namespace Renci.SshNet.Compression { /// - /// Specifies compression modes + /// Specifies compression modes. /// public enum CompressionMode { diff --git a/src/Renci.SshNet/Compression/Compressor.cs b/src/Renci.SshNet/Compression/Compressor.cs index b63a292e..63eb3e33 100644 --- a/src/Renci.SshNet/Compression/Compressor.cs +++ b/src/Renci.SshNet/Compression/Compressor.cs @@ -6,7 +6,7 @@ using Renci.SshNet.Security; namespace Renci.SshNet.Compression { /// - /// Represents base class for compression algorithm implementation + /// Represents base class for compression algorithm implementation. /// public abstract class Compressor : Algorithm, IDisposable { @@ -20,7 +20,7 @@ namespace Renci.SshNet.Compression /// Gets or sets a value indicating whether compression is active. /// /// - /// if compression is active; otherwise, . + /// if compression is active; otherwise, . /// protected bool IsActive { get; set; } @@ -42,7 +42,7 @@ namespace Renci.SshNet.Compression } /// - /// Initializes the algorithm + /// Initializes the algorithm. /// /// The session. public virtual void Init(Session session) @@ -54,7 +54,9 @@ namespace Renci.SshNet.Compression /// Compresses the specified data. /// /// Data to compress. - /// Compressed data + /// + /// The compressed data. + /// public virtual byte[] Compress(byte[] data) { return Compress(data, 0, data.Length); @@ -142,7 +144,7 @@ namespace Renci.SshNet.Compression } /// - /// Releases unmanaged and - optionally - managed resources + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) diff --git a/src/Renci.SshNet/Compression/Zlib.cs b/src/Renci.SshNet/Compression/Zlib.cs index 0dc5918c..504697fd 100644 --- a/src/Renci.SshNet/Compression/Zlib.cs +++ b/src/Renci.SshNet/Compression/Zlib.cs @@ -1,7 +1,7 @@ namespace Renci.SshNet.Compression { /// - /// Represents "zlib" compression implementation + /// Represents "zlib" compression implementation. /// internal sealed class Zlib : Compressor { @@ -14,7 +14,7 @@ } /// - /// Initializes the algorithm + /// Initializes the algorithm. /// /// The session. public override void Init(Session session) diff --git a/src/Renci.SshNet/Compression/ZlibOpenSsh.cs b/src/Renci.SshNet/Compression/ZlibOpenSsh.cs index 177d76b4..45bf1165 100644 --- a/src/Renci.SshNet/Compression/ZlibOpenSsh.cs +++ b/src/Renci.SshNet/Compression/ZlibOpenSsh.cs @@ -3,7 +3,7 @@ namespace Renci.SshNet.Compression { /// - /// Represents "zlib@openssh.org" compression implementation + /// Represents "zlib@openssh.org" compression implementation. /// public class ZlibOpenSsh : Compressor { @@ -16,7 +16,7 @@ namespace Renci.SshNet.Compression } /// - /// Initializes the algorithm + /// Initializes the algorithm. /// /// The session. public override void Init(Session session) @@ -32,4 +32,4 @@ namespace Renci.SshNet.Compression Session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived; } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet/Compression/ZlibStream.cs b/src/Renci.SshNet/Compression/ZlibStream.cs index 5c9d3b84..e0924499 100644 --- a/src/Renci.SshNet/Compression/ZlibStream.cs +++ b/src/Renci.SshNet/Compression/ZlibStream.cs @@ -1,11 +1,16 @@ using System.IO; +#pragma warning disable S125 // Sections of code should not be commented out +#pragma warning disable SA1005 // Single line comments should begin with single space + namespace Renci.SshNet.Compression { /// /// Implements Zlib compression algorithm. /// +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix public class ZlibStream +#pragma warning restore CA1711 // Identifiers should not have incorrect suffix { //private readonly Ionic.Zlib.ZlibStream _baseStream; @@ -45,5 +50,9 @@ namespace Renci.SshNet.Compression { //this._baseStream.Write(buffer, offset, count); } +#pragma warning restore SA1005 // Single line comments should begin with single space } } + +#pragma warning restore SA1005 // Single line comments should begin with single space +#pragma warning restore S125 // Sections of code should not be commented out diff --git a/src/Renci.SshNet/Connection/IConnector.cs b/src/Renci.SshNet/Connection/IConnector.cs index e49587b7..6ce454eb 100644 --- a/src/Renci.SshNet/Connection/IConnector.cs +++ b/src/Renci.SshNet/Connection/IConnector.cs @@ -3,10 +3,28 @@ using System.Threading; namespace Renci.SshNet.Connection { + /// + /// Represents a means to connect to a SSH endpoint. + /// internal interface IConnector { + /// + /// Connects to a SSH endpoint using the specified . + /// + /// The to use to establish a connection to a SSH endpoint. + /// + /// A connected to the SSH endpoint represented by the specified . + /// Socket Connect(IConnectionInfo connectionInfo); + /// + /// Asynchronously connects to a SSH endpoint using the specified . + /// + /// The to use to establish a connection to a SSH endpoint. + /// The token to monitor for cancellation requests. + /// + /// A connected to the SSH endpoint represented by the specified . + /// System.Threading.Tasks.Task ConnectAsync(IConnectionInfo connectionInfo, CancellationToken cancellationToken); } } diff --git a/src/Renci.SshNet/Connection/IProtocolVersionExchange.cs b/src/Renci.SshNet/Connection/IProtocolVersionExchange.cs index 252cda98..1cbe3d7f 100644 --- a/src/Renci.SshNet/Connection/IProtocolVersionExchange.cs +++ b/src/Renci.SshNet/Connection/IProtocolVersionExchange.cs @@ -1,5 +1,7 @@ using System; using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; namespace Renci.SshNet.Connection { @@ -19,6 +21,16 @@ namespace Renci.SshNet.Connection /// SshIdentification Start(string clientVersion, Socket socket, TimeSpan timeout); - System.Threading.Tasks.Task StartAsync(string clientVersion, Socket socket, System.Threading.CancellationToken cancellationToken); + /// + /// Asynchronously performs the SSH protocol version exchange. + /// + /// The identification string of the SSH client. + /// A connected to the server. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the SSH protocol version exchange. The value of its + /// contains the SSH identification of the server. + /// + Task StartAsync(string clientVersion, Socket socket, CancellationToken cancellationToken); } } diff --git a/src/Renci.SshNet/Connection/ISocketFactory.cs b/src/Renci.SshNet/Connection/ISocketFactory.cs index 0d76eeb2..a1223318 100644 --- a/src/Renci.SshNet/Connection/ISocketFactory.cs +++ b/src/Renci.SshNet/Connection/ISocketFactory.cs @@ -2,8 +2,22 @@ namespace Renci.SshNet.Connection { + /// + /// Represents a factory to create instances. + /// internal interface ISocketFactory { + /// + /// Creates a with the specified , + /// and that does not use the + /// Nagle algorithm. + /// + /// The . + /// The . + /// The . + /// + /// The . + /// Socket Create(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType); } } diff --git a/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs b/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs index fb0203f1..00c5d057 100644 --- a/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs +++ b/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs @@ -23,7 +23,7 @@ namespace Renci.SshNet.Connection { private const byte Null = 0x00; - private static readonly Regex ServerVersionRe = new Regex("^SSH-(?[^-]+)-(?.+?)([ ](?.+))?$", RegexOptions.Compiled); + private static readonly Regex ServerVersionRe = new Regex("^SSH-(?[^-]+)-(?.+?)([ ](?.+))?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture); /// /// Performs the SSH protocol version exchange. @@ -67,6 +67,16 @@ namespace Renci.SshNet.Connection } } + /// + /// Asynchronously performs the SSH protocol version exchange. + /// + /// The identification string of the SSH client. + /// A connected to the server. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the SSH protocol version exchange. The value of its + /// contains the SSH identification of the server. + /// public async Task StartAsync(string clientVersion, Socket socket, CancellationToken cancellationToken) { // Immediately send the identification string since the spec states both sides MUST send an identification string diff --git a/src/Renci.SshNet/Connection/ProxyConnector.cs b/src/Renci.SshNet/Connection/ProxyConnector.cs index 089ee562..d8261e02 100644 --- a/src/Renci.SshNet/Connection/ProxyConnector.cs +++ b/src/Renci.SshNet/Connection/ProxyConnector.cs @@ -5,6 +5,10 @@ using System.Threading.Tasks; namespace Renci.SshNet.Connection { + /// + /// Represents a connector that uses a proxy server to establish a connection to a given SSH + /// endpoint. + /// internal abstract class ProxyConnector : ConnectorBase { protected ProxyConnector(ISocketFactory socketFactory) @@ -15,18 +19,37 @@ namespace Renci.SshNet.Connection protected abstract void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket); // ToDo: Performs async/sync fallback, true async version should be implemented in derived classes - protected virtual Task HandleProxyConnectAsync(IConnectionInfo connectionInfo, Socket socket, CancellationToken cancellationToken) + protected virtual +#if NET || NETSTANDARD2_1_OR_GREATER + async +#endif // NET || NETSTANDARD2_1_OR_GREATER + Task HandleProxyConnectAsync(IConnectionInfo connectionInfo, Socket socket, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - using (cancellationToken.Register(o => ((Socket)o).Dispose(), socket, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(o => ((Socket)o).Dispose(), socket, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(o => ((Socket) o).Dispose(), socket, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { +#pragma warning disable MA0042 // Do not use blocking calls in an async method; false positive caused by https://github.com/meziantou/Meziantou.Analyzer/issues/613 HandleProxyConnect(connectionInfo, socket); +#pragma warning restore MA0042 // Do not use blocking calls in an async method } +#if !NET && !NETSTANDARD2_1_OR_GREATER return Task.CompletedTask; +#endif // !NET && !NETSTANDARD2_1_OR_GREATER } + /// + /// Connects to a SSH endpoint using the specified . + /// + /// The to use to establish a connection to a SSH endpoint. + /// + /// A connected to the SSH endpoint represented by the specified . + /// public override Socket Connect(IConnectionInfo connectionInfo) { var socket = SocketConnect(connectionInfo.ProxyHost, connectionInfo.ProxyPort, connectionInfo.Timeout); @@ -45,6 +68,14 @@ namespace Renci.SshNet.Connection } } + /// + /// Asynchronously connects to a SSH endpoint using the specified . + /// + /// The to use to establish a connection to a SSH endpoint. + /// The token to monitor for cancellation requests. + /// + /// A connected to the SSH endpoint represented by the specified . + /// public override async Task ConnectAsync(IConnectionInfo connectionInfo, CancellationToken cancellationToken) { var socket = await SocketConnectAsync(connectionInfo.ProxyHost, connectionInfo.ProxyPort, cancellationToken).ConfigureAwait(false); diff --git a/src/Renci.SshNet/Connection/SocketFactory.cs b/src/Renci.SshNet/Connection/SocketFactory.cs index d279da28..69863494 100644 --- a/src/Renci.SshNet/Connection/SocketFactory.cs +++ b/src/Renci.SshNet/Connection/SocketFactory.cs @@ -2,11 +2,25 @@ namespace Renci.SshNet.Connection { + /// + /// Represents a factory to create instances. + /// internal sealed class SocketFactory : ISocketFactory { + /// + /// Creates a with the specified , + /// and that does not use the + /// Nagle algorithm. + /// + /// The . + /// The . + /// The . + /// + /// The . + /// public Socket Create(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) { - return new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; + return new Socket(addressFamily, socketType, protocolType) { NoDelay = true }; } } } diff --git a/src/Renci.SshNet/Connection/Socks4Connector.cs b/src/Renci.SshNet/Connection/Socks4Connector.cs index 4da6bf77..d679c17a 100644 --- a/src/Renci.SshNet/Connection/Socks4Connector.cs +++ b/src/Renci.SshNet/Connection/Socks4Connector.cs @@ -62,21 +62,23 @@ namespace Renci.SshNet.Connection var addressBytes = GetSocks4DestinationAddress(hostname); var proxyUserBytes = GetProxyUserBytes(username); - var connectionRequest = new byte - [ - // SOCKS version number - 1 + - // Command code - 1 + - // Port number - 2 + - // IP address - addressBytes.Length + - // Username - proxyUserBytes.Length + - // Null terminator - 1 - ]; + var connectionRequest = new byte[// SOCKS version number + 1 + + + // Command code + 1 + + + // Port number + 2 + + + // IP address + addressBytes.Length + + + // Username + proxyUserBytes.Length + + + // Null terminator + 1]; var index = 0; diff --git a/src/Renci.SshNet/Connection/Socks5Connector.cs b/src/Renci.SshNet/Connection/Socks5Connector.cs index ef9f9974..a1ed5f75 100644 --- a/src/Renci.SshNet/Connection/Socks5Connector.cs +++ b/src/Renci.SshNet/Connection/Socks5Connector.cs @@ -30,10 +30,13 @@ namespace Renci.SshNet.Connection { // SOCKS version number 0x05, + // Number of supported authentication methods 0x02, + // No authentication 0x00, + // Username/Password authentication 0x02 }; @@ -156,19 +159,20 @@ namespace Renci.SshNet.Connection throw new ProxyException("Proxy password is too long."); } - var authenticationRequest = new byte - [ - // Version of the negotiation - 1 + - // Length of the username - 1 + - // Username - username.Length + - // Length of the password - 1 + - // Password - password.Length - ]; + var authenticationRequest = new byte[// Version of the negotiation + 1 + + + // Length of the username + 1 + + + // Username + username.Length + + + // Length of the password + 1 + + + // Password + password.Length]; var index = 0; @@ -195,21 +199,23 @@ namespace Renci.SshNet.Connection { var addressBytes = GetSocks5DestinationAddress(hostname, out var addressType); - var connectionRequest = new byte - [ - // SOCKS version number - 1 + - // Command code - 1 + - // Reserved - 1 + - // Address type - 1 + - // Address - addressBytes.Length + - // Port number - 2 - ]; + var connectionRequest = new byte[// SOCKS version number + 1 + + + // Command code + 1 + + + // Reserved + 1 + + + // Address type + 1 + + + // Address + addressBytes.Length + + + // Port number + 2]; var index = 0; diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index 1f109c27..02e817b2 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -398,8 +398,10 @@ namespace Renci.SshNet { "ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(), data) }, { "ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(), data) }, { "ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(), data) }, - { "rsa-sha2-512", data => { var key = new RsaKey(); return new KeyHostAlgorithm("rsa-sha2-512", key, data, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); }}, - { "rsa-sha2-256", data => { var key = new RsaKey(); return new KeyHostAlgorithm("rsa-sha2-256", key, data, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); }}, +#pragma warning disable SA1107 // Code should not contain multiple statements on one line + { "rsa-sha2-512", data => { var key = new RsaKey(); return new KeyHostAlgorithm("rsa-sha2-512", key, data, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); } }, + { "rsa-sha2-256", data => { var key = new RsaKey(); return new KeyHostAlgorithm("rsa-sha2-256", key, data, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); } }, +#pragma warning restore SA1107 // Code should not contain multiple statements on one line { "ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(), data) }, { "ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(), data) }, }; @@ -470,14 +472,28 @@ namespace Renci.SshNet AuthenticationBanner?.Invoke(this, new AuthenticationBannerEventArgs(Username, e.Message.Message, e.Message.Language)); } + /// + /// Creates a none authentication method. + /// + /// + /// A none authentication method. + /// IAuthenticationMethod IConnectionInfoInternal.CreateNoneAuthenticationMethod() { return new NoneAuthenticationMethod(Username); } + /// + /// Gets the supported authentication methods for this connection. + /// + /// + /// The supported authentication methods for this connection. + /// IList IConnectionInfoInternal.AuthenticationMethods { +#pragma warning disable S2365 // Properties should not make collection or array copies get { return AuthenticationMethods.Cast().ToList(); } +#pragma warning restore S2365 // Properties should not make collection or array copies } } } diff --git a/src/Renci.SshNet/ForwardedPortDynamic.cs b/src/Renci.SshNet/ForwardedPortDynamic.cs index be67f628..21ab59a2 100644 --- a/src/Renci.SshNet/ForwardedPortDynamic.cs +++ b/src/Renci.SshNet/ForwardedPortDynamic.cs @@ -361,10 +361,12 @@ namespace Renci.SshNet Closing -= closeClientSocket; } - void closeClientSocket(object _, EventArgs args) +#pragma warning disable SA1300 // Element should begin with upper-case letter + void closeClientSocket(object sender, EventArgs args) { CloseClientSocket(clientSocket); - }; + } +#pragma warning restore SA1300 // Element should begin with upper-case letter } private static void CloseClientSocket(Socket clientSocket) diff --git a/src/Renci.SshNet/ForwardedPortStatus.cs b/src/Renci.SshNet/ForwardedPortStatus.cs index a8ab2224..5695e28c 100644 --- a/src/Renci.SshNet/ForwardedPortStatus.cs +++ b/src/Renci.SshNet/ForwardedPortStatus.cs @@ -34,7 +34,9 @@ namespace Renci.SshNet return forwardedPortStatus._value == _value; } +#pragma warning disable S3875 // "operator==" should not be overloaded on reference types public static bool operator ==(ForwardedPortStatus left, ForwardedPortStatus right) +#pragma warning restore S3875 // "operator==" should not be overloaded on reference types { // check if lhs is null if (left is null) diff --git a/src/Renci.SshNet/HashInfo.cs b/src/Renci.SshNet/HashInfo.cs index 60829f1e..cbbbf5fe 100644 --- a/src/Renci.SshNet/HashInfo.cs +++ b/src/Renci.SshNet/HashInfo.cs @@ -5,7 +5,7 @@ using Renci.SshNet.Common; namespace Renci.SshNet { /// - /// Holds information about key size and cipher to use + /// Holds information about key size and cipher to use. /// public class HashInfo { @@ -23,7 +23,7 @@ namespace Renci.SshNet public Func HashAlgorithm { get; private set; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Size of the key. /// The hash algorithm to use for a given key. diff --git a/src/Renci.SshNet/IBaseClient.cs b/src/Renci.SshNet/IBaseClient.cs index ff6d30bd..add066f7 100644 --- a/src/Renci.SshNet/IBaseClient.cs +++ b/src/Renci.SshNet/IBaseClient.cs @@ -10,7 +10,7 @@ namespace Renci.SshNet /// /// Serves as base class for client implementations, provides common client functionality. /// - public interface IBaseClient + public interface IBaseClient : IDisposable { /// /// Gets the connection info. @@ -87,11 +87,6 @@ namespace Renci.SshNet /// The method was called after the client was disposed. void Disconnect(); - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - void Dispose(); - /// /// Sends a keep-alive message to the server. /// diff --git a/src/Renci.SshNet/IClientAuthentication.cs b/src/Renci.SshNet/IClientAuthentication.cs index 5dbfccc3..15124fc7 100644 --- a/src/Renci.SshNet/IClientAuthentication.cs +++ b/src/Renci.SshNet/IClientAuthentication.cs @@ -1,7 +1,23 @@ -namespace Renci.SshNet +using System; + +using Renci.SshNet.Common; + +namespace Renci.SshNet { + /// + /// Represents a mechanism to authenticate a given client. + /// internal interface IClientAuthentication { + /// + /// Attempts to perform authentication for a given using the + /// of the specified + /// . + /// + /// A to use for authenticating. + /// The for which to perform authentication. + /// or is . + /// Failed to Authenticate the client. void Authenticate(IConnectionInfoInternal connectionInfo, ISession session); } } diff --git a/src/Renci.SshNet/IConnectionInfo.cs b/src/Renci.SshNet/IConnectionInfo.cs index 77908d2d..a0f2898e 100644 --- a/src/Renci.SshNet/IConnectionInfo.cs +++ b/src/Renci.SshNet/IConnectionInfo.cs @@ -3,39 +3,10 @@ using System.Collections.Generic; using System.Text; using Renci.SshNet.Common; -using Renci.SshNet.Messages.Authentication; using Renci.SshNet.Messages.Connection; namespace Renci.SshNet { - internal interface IConnectionInfoInternal : IConnectionInfo - { - /// - /// Signals that an authentication banner message was received from the server. - /// - /// The session in which the banner message was received. - /// The banner message. - void UserAuthenticationBannerReceived(object sender, MessageEventArgs e); - - /// - /// Gets the supported authentication methods for this connection. - /// - /// - /// The supported authentication methods for this connection. - /// - IList AuthenticationMethods { get; } - - /// - /// Creates a for the credentials represented - /// by the current . - /// - /// - /// A for the credentials represented by the - /// current . - /// - IAuthenticationMethod CreateNoneAuthenticationMethod(); - } - /// /// Represents remote connection information. /// diff --git a/src/Renci.SshNet/IConnectionInfoInternal.cs b/src/Renci.SshNet/IConnectionInfoInternal.cs new file mode 100644 index 00000000..053e36dd --- /dev/null +++ b/src/Renci.SshNet/IConnectionInfoInternal.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +using Renci.SshNet.Messages.Authentication; + +namespace Renci.SshNet +{ + /// + /// Represents remote connection information. + /// + internal interface IConnectionInfoInternal : IConnectionInfo + { + /// + /// Signals that an authentication banner message was received from the server. + /// + /// The session in which the banner message was received. + /// The banner message. + void UserAuthenticationBannerReceived(object sender, MessageEventArgs e); + + /// + /// Gets the supported authentication methods for this connection. + /// + /// + /// The supported authentication methods for this connection. + /// + IList AuthenticationMethods { get; } + + /// + /// Creates a for the credentials represented + /// by the current . + /// + /// + /// A for the credentials represented by the + /// current . + /// + IAuthenticationMethod CreateNoneAuthenticationMethod(); + } +} diff --git a/src/Renci.SshNet/IForwardedPort.cs b/src/Renci.SshNet/IForwardedPort.cs index 173613b3..92e49ee3 100644 --- a/src/Renci.SshNet/IForwardedPort.cs +++ b/src/Renci.SshNet/IForwardedPort.cs @@ -1,7 +1,5 @@ using System; -using Renci.SshNet.Common; - namespace Renci.SshNet { /// @@ -13,33 +11,5 @@ namespace Renci.SshNet /// The event occurs as the forwarded port is being stopped. /// event EventHandler Closing; - - /// - /// Occurs when an exception is thrown. - /// - event EventHandler Exception; - - /// - /// Occurs when a port forwarding request is received. - /// - event EventHandler RequestReceived; - - /// - /// Gets a value indicating whether port forwarding is started. - /// - /// - /// if port forwarding is started; otherwise, . - /// - bool IsStarted { get; } - - /// - /// Starts port forwarding. - /// - void Start(); - - /// - /// Stops port forwarding. - /// - void Stop(); } } diff --git a/src/Renci.SshNet/ISession.cs b/src/Renci.SshNet/ISession.cs index fbac0570..5a035b10 100644 --- a/src/Renci.SshNet/ISession.cs +++ b/src/Renci.SshNet/ISession.cs @@ -17,7 +17,7 @@ namespace Renci.SshNet internal interface ISession : IDisposable { /// - /// Gets or sets the connection info. + /// Gets the connection info. /// /// The connection info. IConnectionInfo ConnectionInfo { get; } @@ -86,6 +86,9 @@ namespace Renci.SshNet /// /// Creates a "forwarded-tcpip" SSH channel. /// + /// The number of the remote channel. + /// The window size of the remote channel. + /// The data packet size of the remote channel. /// /// A new "forwarded-tcpip" SSH channel. /// diff --git a/src/Renci.SshNet/ISftpClient.cs b/src/Renci.SshNet/ISftpClient.cs index f9015f5a..efbce934 100644 --- a/src/Renci.SshNet/ISftpClient.cs +++ b/src/Renci.SshNet/ISftpClient.cs @@ -13,7 +13,7 @@ namespace Renci.SshNet /// /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. /// - public interface ISftpClient : IBaseClient, IDisposable + public interface ISftpClient : IBaseClient { /// /// Gets or sets the maximum size of the buffer in bytes. @@ -78,12 +78,12 @@ namespace Renci.SshNet /// /// The file to append the lines to. The file is created if it does not already exist. /// The lines to append to the file. - /// is -or- is . + /// is . -or- is . /// Client is not connected. /// The specified path is invalid, or its directory was not found on the remote host. /// The method was called after the client was disposed. /// - /// The characters are written to the file using UTF-8 encoding without a Byte-Order Mark (BOM) + /// The characters are written to the file using UTF-8 encoding without a Byte-Order Mark (BOM). /// void AppendAllLines(string path, IEnumerable contents); @@ -239,7 +239,7 @@ namespace Renci.SshNet /// /// is . /// is or contains only whitespace. - /// If a problem occurs while copying the file + /// If a problem occurs while copying the file. IAsyncResult BeginSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, AsyncCallback asyncCallback, object state); /// @@ -568,7 +568,7 @@ namespace Renci.SshNet void EndUploadFile(IAsyncResult asyncResult); /// - /// Checks whether file or directory exists; + /// Checks whether file or directory exists. /// /// The path. /// @@ -592,7 +592,9 @@ namespace Renci.SshNet /// was not found on the remote host. /// is . /// The method was called after the client was disposed. +#pragma warning disable CA1716 // Identifiers should not match keywords ISftpFile Get(string path); +#pragma warning restore CA1716 // Identifiers should not match keywords /// /// Gets the of the file on the path. @@ -897,6 +899,19 @@ namespace Renci.SshNet /// The method was called after the client was disposed. void RenameFile(string oldPath, string newPath); + /// + /// Renames remote file from old path to new path. + /// + /// Path to the old file location. + /// Path to the new file location. + /// if set to then perform a posix rename. + /// is . -or- or is . + /// Client is not connected. + /// Permission to rename the file was denied by the remote host. -or- A SSH command was denied by the server. + /// A SSH error where is the message from the remote host. + /// The method was called after the client was disposed. + void RenameFile(string oldPath, string newPath, bool isPosix); + /// /// Asynchronously renames remote file from old path to new path. /// @@ -911,19 +926,6 @@ namespace Renci.SshNet /// The method was called after the client was disposed. Task RenameFileAsync(string oldPath, string newPath, CancellationToken cancellationToken); - /// - /// Renames remote file from old path to new path. - /// - /// Path to the old file location. - /// Path to the new file location. - /// if set to then perform a posix rename. - /// is . -or- or is . - /// Client is not connected. - /// Permission to rename the file was denied by the remote host. -or- A SSH command was denied by the server. - /// A SSH error where is the message from the remote host. - /// The method was called after the client was disposed. - void RenameFile(string oldPath, string newPath, bool isPosix); - /// /// Sets the date and time the specified file was last accessed. /// @@ -951,7 +953,7 @@ namespace Renci.SshNet /// The file for which to set the date and time information. /// A containing the value to set for the last write date and time of path. This value is expressed in UTC time. void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc); - + /// /// Sets the specified of the file on the specified path. /// @@ -986,7 +988,7 @@ namespace Renci.SshNet /// is . /// is or contains only whitespace. /// was not found on the remote host. - /// If a problem occurs while copying the file + /// If a problem occurs while copying the file. IEnumerable SynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern); /// diff --git a/src/Renci.SshNet/ISubsystemSession.cs b/src/Renci.SshNet/ISubsystemSession.cs index af1ce31e..f1fe6f91 100644 --- a/src/Renci.SshNet/ISubsystemSession.cs +++ b/src/Renci.SshNet/ISubsystemSession.cs @@ -13,7 +13,7 @@ namespace Renci.SshNet /// Gets or set the number of seconds to wait for an operation to complete. /// /// - /// The number of seconds to wait for an operation to complete, or -1 to wait indefinitely. + /// The number of seconds to wait for an operation to complete, or -1 to wait indefinitely. /// int OperationTimeout { get; } @@ -41,7 +41,7 @@ namespace Renci.SshNet /// Waits a specified time for a given to get signaled. /// /// The handle to wait for. - /// The number of millieseconds wait for to get signaled, or -1 to wait indefinitely. + /// The number of millieseconds wait for to get signaled, or -1 to wait indefinitely. /// The connection was closed by the server. /// The channel was closed. /// The handle did not get signaled within the specified timeout. @@ -52,7 +52,7 @@ namespace Renci.SshNet /// 32-bit signed integer to specify the time interval in milliseconds. /// /// The handle to wait for. - /// To number of milliseconds to wait for to get signaled, or -1 to wait indefinitely. + /// To number of milliseconds to wait for to get signaled, or -1 to wait indefinitely. /// /// if received a signal within the specified timeout; /// otherwise, . @@ -72,7 +72,7 @@ namespace Renci.SshNet /// /// The first handle to wait for. /// The second handle to wait for. - /// To number of milliseconds to wait for a to get signaled, or -1 to wait indefinitely. + /// To number of milliseconds to wait for a to get signaled, or -1 to wait indefinitely. /// /// 0 if received a signal within the specified timeout and 1 /// if received a signal within the specified timeout, or @@ -98,7 +98,7 @@ namespace Renci.SshNet /// integer to specify the time interval. /// /// A array - constructed using - containing the objects to wait for. - /// To number of milliseconds to wait for a to get signaled, or -1 to wait indefinitely. + /// To number of milliseconds to wait for a to get signaled, or -1 to wait indefinitely. /// /// The array index of the first non-system object that satisfied the wait. /// diff --git a/src/Renci.SshNet/KeyboardInteractiveConnectionInfo.cs b/src/Renci.SshNet/KeyboardInteractiveConnectionInfo.cs index 5c14bf80..2d3825b3 100644 --- a/src/Renci.SshNet/KeyboardInteractiveConnectionInfo.cs +++ b/src/Renci.SshNet/KeyboardInteractiveConnectionInfo.cs @@ -134,12 +134,13 @@ namespace Renci.SshNet kbdInteractive.AuthenticationPrompt += AuthenticationMethod_AuthenticationPrompt; } } - } private void AuthenticationMethod_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) { +#pragma warning disable MA0091 // Sender should be 'this' for instance events AuthenticationPrompt?.Invoke(sender, e); +#pragma warning restore MA0091 // Sender should be 'this' for instance events } /// diff --git a/src/Renci.SshNet/Messages/Authentication/FailureMessage.cs b/src/Renci.SshNet/Messages/Authentication/FailureMessage.cs index e3229287..61379600 100644 --- a/src/Renci.SshNet/Messages/Authentication/FailureMessage.cs +++ b/src/Renci.SshNet/Messages/Authentication/FailureMessage.cs @@ -25,7 +25,7 @@ namespace Renci.SshNet.Messages.Authentication /// Gets a value indicating whether authentication is partially successful. /// /// - /// if partially successful; otherwise, . + /// if partially successful; otherwise, . /// public bool PartialSuccess { get; private set; } @@ -38,7 +38,11 @@ namespace Renci.SshNet.Messages.Authentication PartialSuccess = ReadBoolean(); if (PartialSuccess) { +#if NET || NETSTANDARD2_1_OR_GREATER + Message = string.Join(',', AllowedAuthentications); +#else Message = string.Join(",", AllowedAuthentications); +#endif // NET || NETSTANDARD2_1_OR_GREATER } } @@ -47,7 +51,9 @@ namespace Renci.SshNet.Messages.Authentication /// protected override void SaveData() { +#pragma warning disable MA0025 // Implement the functionality instead of throwing NotImplementedException throw new NotImplementedException(); +#pragma warning restore MA0025 // Implement the functionality instead of throwing NotImplementedException } internal override void Process(Session session) diff --git a/src/Renci.SshNet/Messages/Authentication/InformationResponseMessage.cs b/src/Renci.SshNet/Messages/Authentication/InformationResponseMessage.cs index 4aa9cda4..9e322734 100644 --- a/src/Renci.SshNet/Messages/Authentication/InformationResponseMessage.cs +++ b/src/Renci.SshNet/Messages/Authentication/InformationResponseMessage.cs @@ -12,7 +12,7 @@ namespace Renci.SshNet.Messages.Authentication /// /// Gets authentication responses. /// - public IList Responses { get; private set; } + public List Responses { get; private set; } /// /// Gets the size of the message in bytes. @@ -48,6 +48,7 @@ namespace Renci.SshNet.Messages.Authentication protected override void SaveData() { Write((uint) Responses.Count); + foreach (var response in Responses) { Write(response); diff --git a/src/Renci.SshNet/Messages/Authentication/RequestMessage.cs b/src/Renci.SshNet/Messages/Authentication/RequestMessage.cs index b535da22..5393df3f 100644 --- a/src/Renci.SshNet/Messages/Authentication/RequestMessage.cs +++ b/src/Renci.SshNet/Messages/Authentication/RequestMessage.cs @@ -108,4 +108,3 @@ namespace Renci.SshNet.Messages.Authentication } } } - diff --git a/src/Renci.SshNet/Messages/Authentication/RequestMessageNone.cs b/src/Renci.SshNet/Messages/Authentication/RequestMessageNone.cs index 1827c329..1f84f416 100644 --- a/src/Renci.SshNet/Messages/Authentication/RequestMessageNone.cs +++ b/src/Renci.SshNet/Messages/Authentication/RequestMessageNone.cs @@ -6,7 +6,7 @@ internal sealed class RequestMessageNone : RequestMessage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Name of the service. /// Authentication username. diff --git a/src/Renci.SshNet/Messages/Connection/ChannelCloseMessage.cs b/src/Renci.SshNet/Messages/Connection/ChannelCloseMessage.cs index 09a6ba83..dfb43013 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelCloseMessage.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelCloseMessage.cs @@ -11,7 +11,6 @@ /// public ChannelCloseMessage() { - } /// diff --git a/src/Renci.SshNet/Messages/Connection/ChannelEofMessage.cs b/src/Renci.SshNet/Messages/Connection/ChannelEofMessage.cs index 469b7b60..0ad02824 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelEofMessage.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelEofMessage.cs @@ -11,7 +11,6 @@ /// public ChannelEofMessage() { - } /// diff --git a/src/Renci.SshNet/Messages/Connection/ChannelFailureMessage.cs b/src/Renci.SshNet/Messages/Connection/ChannelFailureMessage.cs index f011f992..5987534c 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelFailureMessage.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelFailureMessage.cs @@ -11,7 +11,6 @@ /// public ChannelFailureMessage() { - } /// diff --git a/src/Renci.SshNet/Messages/Connection/ChannelOpen/ChannelOpenInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelOpen/ChannelOpenInfo.cs index c9c33405..5c6e3c40 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelOpen/ChannelOpenInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelOpen/ChannelOpenInfo.cs @@ -3,7 +3,7 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Base class for open channel messages + /// Base class for open channel messages. /// public abstract class ChannelOpenInfo : SshData { diff --git a/src/Renci.SshNet/Messages/Connection/ChannelOpen/DirectTcpipChannelInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelOpen/DirectTcpipChannelInfo.cs index c2ac16c3..aefb316a 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelOpen/DirectTcpipChannelInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelOpen/DirectTcpipChannelInfo.cs @@ -3,7 +3,7 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Used to open "direct-tcpip" channel type + /// Used to open "direct-tcpip" channel type. /// internal sealed class DirectTcpipChannelInfo : ChannelOpenInfo { @@ -11,7 +11,7 @@ namespace Renci.SshNet.Messages.Connection private byte[] _originatorAddress; /// - /// Specifies channel open type + /// Specifies channel open type. /// public const string NAME = "direct-tcpip"; diff --git a/src/Renci.SshNet/Messages/Connection/ChannelOpen/ForwardedTcpipChannelInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelOpen/ForwardedTcpipChannelInfo.cs index b7d1efbf..f8bb6742 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelOpen/ForwardedTcpipChannelInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelOpen/ForwardedTcpipChannelInfo.cs @@ -3,10 +3,15 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Used to open "forwarded-tcpip" channel type + /// Used to open "forwarded-tcpip" channel type. /// internal sealed class ForwardedTcpipChannelInfo : ChannelOpenInfo { + /// + /// Specifies channel open type. + /// + public const string NAME = "forwarded-tcpip"; + private byte[] _connectedAddress; private byte[] _originatorAddress; @@ -21,8 +26,8 @@ namespace Renci.SshNet.Messages.Connection } /// - /// Initializes a new instance with the specified connector - /// address and port, and originator address and port. + /// Initializes a new instance of the class with the + /// specified connector address and port, and originator address and port. /// public ForwardedTcpipChannelInfo(string connectedAddress, uint connectedPort, string originatorAddress, uint originatorPort) { @@ -32,11 +37,6 @@ namespace Renci.SshNet.Messages.Connection OriginatorPort = originatorPort; } - /// - /// Specifies channel open type - /// - public const string NAME = "forwarded-tcpip"; - /// /// Gets the type of the channel to open. /// @@ -51,6 +51,9 @@ namespace Renci.SshNet.Messages.Connection /// /// Gets the connected address. /// + /// + /// The connected address. + /// public string ConnectedAddress { get { return Utf8.GetString(_connectedAddress, 0, _connectedAddress.Length); } @@ -60,11 +63,17 @@ namespace Renci.SshNet.Messages.Connection /// /// Gets the connected port. /// + /// + /// The connected port. + /// public uint ConnectedPort { get; private set; } /// /// Gets the originator address. /// + /// + /// The originator address. + /// public string OriginatorAddress { get { return Utf8.GetString(_originatorAddress, 0, _originatorAddress.Length); } @@ -74,6 +83,9 @@ namespace Renci.SshNet.Messages.Connection /// /// Gets the originator port. /// + /// + /// The originator port. + /// public uint OriginatorPort { get; private set; } /// diff --git a/src/Renci.SshNet/Messages/Connection/ChannelOpen/SessionChannelOpenInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelOpen/SessionChannelOpenInfo.cs index c628b33d..f20df583 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelOpen/SessionChannelOpenInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelOpen/SessionChannelOpenInfo.cs @@ -3,12 +3,12 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Used to open "session" channel type + /// Used to open "session" channel type. /// internal sealed class SessionChannelOpenInfo : ChannelOpenInfo { /// - /// Specifies channel open type + /// Specifies channel open type. /// public const string Name = "session"; diff --git a/src/Renci.SshNet/Messages/Connection/ChannelOpen/X11ChannelOpenInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelOpen/X11ChannelOpenInfo.cs index 4ae7237c..d071799c 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelOpen/X11ChannelOpenInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelOpen/X11ChannelOpenInfo.cs @@ -3,14 +3,14 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Used to open "x11" channel type + /// Used to open "x11" channel type. /// internal sealed class X11ChannelOpenInfo : ChannelOpenInfo { private byte[] _originatorAddress; /// - /// Specifies channel open type + /// Specifies channel open type. /// public const string Name = "x11"; diff --git a/src/Renci.SshNet/Messages/Connection/ChannelOpenFailureMessage.cs b/src/Renci.SshNet/Messages/Connection/ChannelOpenFailureMessage.cs index 2a288807..4488f9f5 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelOpenFailureMessage.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelOpenFailureMessage.cs @@ -62,7 +62,6 @@ /// public ChannelOpenFailureMessage() { - } /// diff --git a/src/Renci.SshNet/Messages/Connection/ChannelOpenFailureReasons.cs b/src/Renci.SshNet/Messages/Connection/ChannelOpenFailureReasons.cs index 94bbf8d3..0fbeaf4d 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelOpenFailureReasons.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelOpenFailureReasons.cs @@ -6,19 +6,22 @@ internal enum ChannelOpenFailureReasons : uint { /// - /// SSH_OPEN_ADMINISTRATIVELY_PROHIBITED + /// SSH_OPEN_ADMINISTRATIVELY_PROHIBITED. /// AdministativelyProhibited = 1, + /// - /// SSH_OPEN_CONNECT_FAILED + /// SSH_OPEN_CONNECT_FAILED. /// ConnectFailed = 2, + /// - /// SSH_OPEN_UNKNOWN_CHANNEL_TYPE + /// SSH_OPEN_UNKNOWN_CHANNEL_TYPE. /// UnknownChannelType = 3, + /// - /// SSH_OPEN_RESOURCE_SHORTAGE + /// SSH_OPEN_RESOURCE_SHORTAGE. /// ResourceShortage = 4 } diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/BreakRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/BreakRequestInfo.cs index 472f8cb8..c36655e8 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/BreakRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/BreakRequestInfo.cs @@ -1,12 +1,12 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "break" type channel request information + /// Represents "break" type channel request information. /// internal sealed class BreakRequestInfo : RequestInfo { /// - /// Channel request name + /// Channel request name. /// public const string Name = "break"; @@ -43,7 +43,7 @@ } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public BreakRequestInfo() { @@ -51,7 +51,7 @@ } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Length of the break. public BreakRequestInfo(uint breakLength) diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/ChannelRequestMessage.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/ChannelRequestMessage.cs index ab4d99e9..fd9ad03b 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/ChannelRequestMessage.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/ChannelRequestMessage.cs @@ -17,7 +17,10 @@ /// public string RequestName { - get { return _requestName; } + get + { + return _requestName; + } private set { _requestName = value; @@ -53,7 +56,7 @@ /// public ChannelRequestMessage() { - // Required for dynamically loading request type when it comes from the server + // Required for dynamically loading request type when it comes from the server } /// diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/EndOfWriteRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/EndOfWriteRequestInfo.cs index e5e3735a..ac4fc0eb 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/EndOfWriteRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/EndOfWriteRequestInfo.cs @@ -1,12 +1,12 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "eow@openssh.com" type channel request information + /// Represents "eow@openssh.com" type channel request information. /// public class EndOfWriteRequestInfo : RequestInfo { /// - /// Channel request name + /// Channel request name. /// public const string Name = "eow@openssh.com"; diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/EnvironmentVariableRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/EnvironmentVariableRequestInfo.cs index 6901f324..d04f80ef 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/EnvironmentVariableRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/EnvironmentVariableRequestInfo.cs @@ -1,7 +1,7 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "env" type channel request information + /// Represents "env" type channel request information. /// internal sealed class EnvironmentVariableRequestInfo : RequestInfo { @@ -9,7 +9,7 @@ private byte[] _variableValue; /// - /// Channel request name + /// Channel request name. /// public const string Name = "env"; @@ -25,7 +25,7 @@ } /// - /// Gets or sets the name of the variable. + /// Gets the name of the variable. /// /// /// The name of the variable. @@ -36,7 +36,7 @@ } /// - /// Gets or sets the variable value. + /// Gets the value of the variable. /// /// /// The variable value. diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/ExitSignalRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/ExitSignalRequestInfo.cs index 9518b9e5..da17aee1 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/ExitSignalRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/ExitSignalRequestInfo.cs @@ -1,7 +1,7 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "exit-signal" type channel request information + /// Represents "exit-signal" type channel request information. /// internal sealed class ExitSignalRequestInfo : RequestInfo { @@ -10,7 +10,7 @@ private byte[] _language; /// - /// Channel request name + /// Channel request name. /// public const string Name = "exit-signal"; @@ -41,7 +41,7 @@ /// Gets a value indicating whether core is dumped. /// /// - /// if core is dumped; otherwise, . + /// if core is dumped; otherwise, . /// public bool CoreDumped { get; private set; } diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/ExitStatusRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/ExitStatusRequestInfo.cs index 9aa92f96..3d465179 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/ExitStatusRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/ExitStatusRequestInfo.cs @@ -1,7 +1,7 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "exit-status" type channel request information + /// Represents "exit-status" type channel request information. /// internal sealed class ExitStatusRequestInfo : RequestInfo { @@ -30,7 +30,7 @@ { get { - var capacity = base.BufferCapacity; + var capacity = base.BufferCapacity; capacity += 4; // ExitStatus return capacity; } diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/KeepAliveRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/KeepAliveRequestInfo.cs index 9bf303d6..75b88df8 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/KeepAliveRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/KeepAliveRequestInfo.cs @@ -1,12 +1,12 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "keepalive@openssh.com" type channel request information + /// Represents "keepalive@openssh.com" type channel request information. /// public class KeepAliveRequestInfo : RequestInfo { /// - /// Channel request name + /// Channel request name. /// public const string Name = "keepalive@openssh.com"; @@ -22,7 +22,7 @@ } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public KeepAliveRequestInfo() { diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/PseudoTerminalInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/PseudoTerminalRequestInfo.cs similarity index 98% rename from src/Renci.SshNet/Messages/Connection/ChannelRequest/PseudoTerminalInfo.cs rename to src/Renci.SshNet/Messages/Connection/ChannelRequest/PseudoTerminalRequestInfo.cs index fbe5a11d..82df2712 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/PseudoTerminalInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/PseudoTerminalRequestInfo.cs @@ -141,7 +141,7 @@ namespace Renci.SshNet.Messages.Connection // write total length of encoded terminal modes, which is 1 bytes for the opcode / terminal mode // and 4 bytes for the uint argument for each entry; the encoded terminal modes are terminated by // opcode TTY_OP_END (which is 1 byte) - Write((uint) TerminalModeValues.Count*(1 + 4) + 1); + Write(((uint) TerminalModeValues.Count*(1 + 4)) + 1); foreach (var item in TerminalModeValues) { diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/RequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/RequestInfo.cs index 103e412b..546f7ec6 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/RequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/RequestInfo.cs @@ -19,7 +19,7 @@ namespace Renci.SshNet.Messages.Connection /// Gets or sets a value indicating whether reply message is needed. /// /// - /// if reply message is needed; otherwise, . + /// if reply message is needed; otherwise, . /// public bool WantReply { get; protected set; } diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/ShellRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/ShellRequestInfo.cs index 42fc9cf1..92ed4fd6 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/ShellRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/ShellRequestInfo.cs @@ -1,12 +1,12 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "shell" type channel request information + /// Represents "shell" type channel request information. /// internal sealed class ShellRequestInfo : RequestInfo { /// - /// Channel request name + /// Channel request name. /// public const string Name = "shell"; diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/SignalRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/SignalRequestInfo.cs index bb69e5ed..c16c842c 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/SignalRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/SignalRequestInfo.cs @@ -1,7 +1,7 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "signal" type channel request information + /// Represents "signal" type channel request information. /// internal sealed class SignalRequestInfo : RequestInfo { diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/SubsystemRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/SubsystemRequestInfo.cs index 6871b9fe..0013792c 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/SubsystemRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/SubsystemRequestInfo.cs @@ -1,14 +1,14 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "subsystem" type channel request information + /// Represents "subsystem" type channel request information. /// internal sealed class SubsystemRequestInfo : RequestInfo { private byte[] _subsystemName; /// - /// Channel request name + /// Channel request name. /// public const string Name = "subsystem"; diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/WindowChangeRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/WindowChangeRequestInfo.cs index edcd9af5..34e4c2cb 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/WindowChangeRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/WindowChangeRequestInfo.cs @@ -1,12 +1,12 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "window-change" type channel request information + /// Represents "window-change" type channel request information. /// internal sealed class WindowChangeRequestInfo : RequestInfo { /// - /// Channe request name + /// Channe request name. /// public const string Name = "window-change"; diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/X11ForwardingRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/X11ForwardingRequestInfo.cs index 794c1da5..c7f38c94 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/X11ForwardingRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/X11ForwardingRequestInfo.cs @@ -32,7 +32,7 @@ public bool IsSingleConnection { get; set; } /// - /// Gets or sets the authentication protocol. + /// Gets the authentication protocol. /// /// /// The authentication protocol. diff --git a/src/Renci.SshNet/Messages/Connection/ChannelRequest/XonXoffRequestInfo.cs b/src/Renci.SshNet/Messages/Connection/ChannelRequest/XonXoffRequestInfo.cs index 60924799..2cf9a19c 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelRequest/XonXoffRequestInfo.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelRequest/XonXoffRequestInfo.cs @@ -1,7 +1,7 @@ namespace Renci.SshNet.Messages.Connection { /// - /// Represents "xon-xoff" type channel request information + /// Represents "xon-xoff" type channel request information. /// internal sealed class XonXoffRequestInfo : RequestInfo { @@ -25,7 +25,7 @@ /// Gets or sets a value indicating whether client can do. /// /// - /// if client can do; otherwise, . + /// if client can do; otherwise, . /// public bool ClientCanDo { get; set; } diff --git a/src/Renci.SshNet/Messages/Connection/ChannelSuccessMessage.cs b/src/Renci.SshNet/Messages/Connection/ChannelSuccessMessage.cs index d6f21e9a..50d302f2 100644 --- a/src/Renci.SshNet/Messages/Connection/ChannelSuccessMessage.cs +++ b/src/Renci.SshNet/Messages/Connection/ChannelSuccessMessage.cs @@ -11,7 +11,6 @@ /// public ChannelSuccessMessage() { - } /// diff --git a/src/Renci.SshNet/Messages/Connection/GlobalRequestMessage.cs b/src/Renci.SshNet/Messages/Connection/GlobalRequestMessage.cs index cbd72795..6e0ca3a2 100644 --- a/src/Renci.SshNet/Messages/Connection/GlobalRequestMessage.cs +++ b/src/Renci.SshNet/Messages/Connection/GlobalRequestMessage.cs @@ -23,7 +23,7 @@ /// Gets a value indicating whether message reply should be sent.. /// /// - /// if message reply should be sent; otherwise, . + /// if message reply should be sent; otherwise, . /// public bool WantReply { get; private set; } diff --git a/src/Renci.SshNet/Messages/Connection/GlobalRequestName.cs b/src/Renci.SshNet/Messages/Connection/GlobalRequestName.cs index 6a1d0e27..263278c0 100644 --- a/src/Renci.SshNet/Messages/Connection/GlobalRequestName.cs +++ b/src/Renci.SshNet/Messages/Connection/GlobalRequestName.cs @@ -6,11 +6,12 @@ public enum GlobalRequestName { /// - /// tcpip-forward + /// tcpip-forward. /// TcpIpForward, + /// - /// cancel-tcpip-forward + /// cancel-tcpip-forward. /// CancelTcpIpForward, } diff --git a/src/Renci.SshNet/Messages/Message.cs b/src/Renci.SshNet/Messages/Message.cs index d2fc3274..ebe3a2e7 100644 --- a/src/Renci.SshNet/Messages/Message.cs +++ b/src/Renci.SshNet/Messages/Message.cs @@ -8,7 +8,7 @@ using Renci.SshNet.Compression; namespace Renci.SshNet.Messages { /// - /// Base class for all SSH protocol messages + /// Base class for all SSH protocol messages. /// public abstract class Message : SshData { @@ -29,6 +29,7 @@ namespace Renci.SshNet.Messages /// /// Writes the message to the specified . /// + /// The to write the message to. protected override void WriteBytes(SshDataStream stream) { var enumerator = GetType().GetCustomAttributes(inherit: true).GetEnumerator(); @@ -55,58 +56,61 @@ namespace Renci.SshNet.Messages var messageLength = BufferCapacity; - SshDataStream sshDataStream; - if (messageLength == -1 || compressor != null) { - sshDataStream = new SshDataStream(DefaultCapacity); - - // skip: - // * 4 bytes for the outbound packet sequence - // * 4 bytes for the packet data length - // * one byte for the packet padding length - _ = sshDataStream.Seek(outboundPacketSequenceSize + 4 + 1, SeekOrigin.Begin); - - if (compressor != null) + using (var sshDataStream = new SshDataStream(DefaultCapacity)) { - // obtain uncompressed message payload - var uncompressedDataStream = new SshDataStream(messageLength != -1 ? messageLength : DefaultCapacity); - WriteBytes(uncompressedDataStream); + // skip: + // * 4 bytes for the outbound packet sequence + // * 4 bytes for the packet data length + // * one byte for the packet padding length + _ = sshDataStream.Seek(outboundPacketSequenceSize + 4 + 1, SeekOrigin.Begin); - // compress message payload - var compressedMessageData = compressor.Compress(uncompressedDataStream.ToArray()); + if (compressor != null) + { + // obtain uncompressed message payload + using (var uncompressedDataStream = new SshDataStream(messageLength != -1 ? messageLength : DefaultCapacity)) + { + WriteBytes(uncompressedDataStream); - // add compressed message payload - sshDataStream.Write(compressedMessageData, 0, compressedMessageData.Length); + // compress message payload + var compressedMessageData = compressor.Compress(uncompressedDataStream.ToArray()); + + // add compressed message payload + sshDataStream.Write(compressedMessageData, 0, compressedMessageData.Length); + } + } + else + { + // add message payload + WriteBytes(sshDataStream); + } + + messageLength = (int) sshDataStream.Length - (outboundPacketSequenceSize + 4 + 1); + + var packetLength = messageLength + 4 + 1; + + // determine the padding length + var paddingLength = GetPaddingLength(paddingMultiplier, packetLength); + + // add padding bytes + var paddingBytes = new byte[paddingLength]; + CryptoAbstraction.GenerateRandom(paddingBytes); + sshDataStream.Write(paddingBytes, 0, paddingLength); + + var packetDataLength = GetPacketDataLength(messageLength, paddingLength); + + // skip bytes for outbound packet sequence + _ = sshDataStream.Seek(outboundPacketSequenceSize, SeekOrigin.Begin); + + // add packet data length + sshDataStream.Write(packetDataLength); + + // add packet padding length + sshDataStream.WriteByte(paddingLength); + + return sshDataStream.ToArray(); } - else - { - // add message payload - WriteBytes(sshDataStream); - } - - messageLength = (int) sshDataStream.Length - (outboundPacketSequenceSize + 4 + 1); - - var packetLength = messageLength + 4 + 1; - - // determine the padding length - var paddingLength = GetPaddingLength(paddingMultiplier, packetLength); - - // add padding bytes - var paddingBytes = new byte[paddingLength]; - CryptoAbstraction.GenerateRandom(paddingBytes); - sshDataStream.Write(paddingBytes, 0, paddingLength); - - var packetDataLength = GetPacketDataLength(messageLength, paddingLength); - - // skip bytes for outbound packet sequence - _ = sshDataStream.Seek(outboundPacketSequenceSize, SeekOrigin.Begin); - - // add packet data length - sshDataStream.Write(packetDataLength); - - // add packet padding length - sshDataStream.WriteByte(paddingLength); } else { @@ -118,27 +122,28 @@ namespace Renci.SshNet.Messages var packetDataLength = GetPacketDataLength(messageLength, paddingLength); // lets construct an SSH data stream of the exact size required - sshDataStream = new SshDataStream(packetLength + paddingLength + outboundPacketSequenceSize); + using (var sshDataStream = new SshDataStream(packetLength + paddingLength + outboundPacketSequenceSize)) + { + // skip bytes for outbound packet sequenceSize + _ = sshDataStream.Seek(outboundPacketSequenceSize, SeekOrigin.Begin); - // skip bytes for outbound packet sequenceSize - _ = sshDataStream.Seek(outboundPacketSequenceSize, SeekOrigin.Begin); + // add packet data length + sshDataStream.Write(packetDataLength); - // add packet data length - sshDataStream.Write(packetDataLength); + // add packet padding length + sshDataStream.WriteByte(paddingLength); - // add packet padding length - sshDataStream.WriteByte(paddingLength); + // add message payload + WriteBytes(sshDataStream); - // add message payload - WriteBytes(sshDataStream); + // add padding bytes + var paddingBytes = new byte[paddingLength]; + CryptoAbstraction.GenerateRandom(paddingBytes); + sshDataStream.Write(paddingBytes, 0, paddingLength); - // add padding bytes - var paddingBytes = new byte[paddingLength]; - CryptoAbstraction.GenerateRandom(paddingBytes); - sshDataStream.Write(paddingBytes, 0, paddingLength); + return sshDataStream.ToArray(); + } } - - return sshDataStream.ToArray(); } private static uint GetPacketDataLength(int messageLength, byte paddingLength) diff --git a/src/Renci.SshNet/Messages/MessageAttribute.cs b/src/Renci.SshNet/Messages/MessageAttribute.cs index 10c0cdef..f94d13c3 100644 --- a/src/Renci.SshNet/Messages/MessageAttribute.cs +++ b/src/Renci.SshNet/Messages/MessageAttribute.cs @@ -2,7 +2,6 @@ namespace Renci.SshNet.Messages { - /// /// Indicates that a class represents SSH message. This class cannot be inherited. /// @@ -10,20 +9,20 @@ namespace Renci.SshNet.Messages public sealed class MessageAttribute : Attribute { /// - /// Gets or sets message name as defined in RFC 4250. + /// Gets the message name as defined in RFC 4250. /// /// /// The name. /// - public string Name { get; set; } + public string Name { get; } /// - /// Gets or sets message number as defined in RFC 4250. + /// Gets the message number as defined in RFC 4250. /// /// /// The number. /// - public byte Number { get; set; } + public byte Number { get; } /// /// Initializes a new instance of the class. diff --git a/src/Renci.SshNet/Messages/ServiceName.cs b/src/Renci.SshNet/Messages/ServiceName.cs index 1c63ddbe..bfd6644c 100644 --- a/src/Renci.SshNet/Messages/ServiceName.cs +++ b/src/Renci.SshNet/Messages/ServiceName.cs @@ -1,17 +1,17 @@ namespace Renci.SshNet.Messages { /// - /// Specifies list of supported services + /// Specifies list of supported services. /// public enum ServiceName { /// - /// ssh-userauth + /// ssh-userauth. /// UserAuthentication, /// - /// ssh-connection + /// ssh-connection. /// Connection } diff --git a/src/Renci.SshNet/Messages/Transport/DebugMessage.cs b/src/Renci.SshNet/Messages/Transport/DebugMessage.cs index b7347283..f550d95f 100644 --- a/src/Renci.SshNet/Messages/Transport/DebugMessage.cs +++ b/src/Renci.SshNet/Messages/Transport/DebugMessage.cs @@ -13,7 +13,7 @@ /// Gets a value indicating whether the message to be always displayed. /// /// - /// if the message always to be displayed; otherwise, . + /// if the message always to be displayed; otherwise, . /// public bool IsAlwaysDisplay { get; private set; } diff --git a/src/Renci.SshNet/Messages/Transport/DisconnectReason.cs b/src/Renci.SshNet/Messages/Transport/DisconnectReason.cs index 35c6f885..cf4119ba 100644 --- a/src/Renci.SshNet/Messages/Transport/DisconnectReason.cs +++ b/src/Renci.SshNet/Messages/Transport/DisconnectReason.cs @@ -9,65 +9,82 @@ /// Disconnect reason is not provided. /// None = 0, + /// - /// SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT + /// SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT. /// HostNotAllowedToConnect = 1, + /// - /// SSH_DISCONNECT_PROTOCOL_ERROR + /// SSH_DISCONNECT_PROTOCOL_ERROR. /// ProtocolError = 2, + /// - /// SSH_DISCONNECT_KEY_EXCHANGE_FAILED + /// SSH_DISCONNECT_KEY_EXCHANGE_FAILED. /// KeyExchangeFailed = 3, + /// - /// SSH_DISCONNECT_RESERVED + /// SSH_DISCONNECT_RESERVED. /// +#pragma warning disable CA1700 // Do not name enum values 'Reserved' Reserved = 4, +#pragma warning restore CA1700 // Do not name enum values 'Reserved' + /// - /// SSH_DISCONNECT_MAC_ERROR + /// SSH_DISCONNECT_MAC_ERROR. /// MacError = 5, + /// - /// SSH_DISCONNECT_COMPRESSION_ERROR + /// SSH_DISCONNECT_COMPRESSION_ERROR. /// CompressionError = 6, + /// - /// SSH_DISCONNECT_SERVICE_NOT_AVAILABLE + /// SSH_DISCONNECT_SERVICE_NOT_AVAILABLE. /// ServiceNotAvailable = 7, + /// - /// SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED + /// SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED. /// ProtocolVersionNotSupported = 8, + /// - /// SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE + /// SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE. /// HostKeyNotVerifiable = 9, + /// - /// SSH_DISCONNECT_CONNECTION_LOST + /// SSH_DISCONNECT_CONNECTION_LOST. /// ConnectionLost = 10, + /// - /// SSH_DISCONNECT_BY_APPLICATION + /// SSH_DISCONNECT_BY_APPLICATION. /// ByApplication = 11, + /// - /// SSH_DISCONNECT_TOO_MANY_CONNECTIONS + /// SSH_DISCONNECT_TOO_MANY_CONNECTIONS. /// TooManyConnections = 12, + /// - /// SSH_DISCONNECT_AUTH_CANCELLED_BY_USER + /// SSH_DISCONNECT_AUTH_CANCELLED_BY_USER. /// AuthenticationCanceledByUser = 13, + /// - /// SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE + /// SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE. /// NoMoreAuthenticationMethodsAvailable = 14, + /// - /// SSH_DISCONNECT_ILLEGAL_USER_NAME + /// SSH_DISCONNECT_ILLEGAL_USER_NAME. /// - IllegalUserName = 15, + IllegalUserName = 15 } } diff --git a/src/Renci.SshNet/Messages/Transport/IKeyExchangedAllowed.cs b/src/Renci.SshNet/Messages/Transport/IKeyExchangedAllowed.cs index b53a09e4..1f06aeb3 100644 --- a/src/Renci.SshNet/Messages/Transport/IKeyExchangedAllowed.cs +++ b/src/Renci.SshNet/Messages/Transport/IKeyExchangedAllowed.cs @@ -1,9 +1,11 @@ namespace Renci.SshNet.Messages.Transport { /// - /// Indicates that message that implement this interface is allowed during key exchange phase + /// Indicates that message that implement this interface is allowed during key exchange phase. /// +#pragma warning disable CA1040 // Avoid empty interfaces public interface IKeyExchangedAllowed +#pragma warning restore CA1040 // Avoid empty interfaces { } } diff --git a/src/Renci.SshNet/Messages/Transport/IgnoreMessage.cs b/src/Renci.SshNet/Messages/Transport/IgnoreMessage.cs index 5fc009a3..5d07f1d0 100644 --- a/src/Renci.SshNet/Messages/Transport/IgnoreMessage.cs +++ b/src/Renci.SshNet/Messages/Transport/IgnoreMessage.cs @@ -18,13 +18,27 @@ namespace Renci.SshNet.Messages.Transport public byte[] Data { get; private set; } /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class. /// public IgnoreMessage() { Data = Array.Empty(); } + /// + /// Initializes a new instance of the class. + /// + /// The data. + public IgnoreMessage(byte[] data) + { + if (data is null) + { + throw new ArgumentNullException(nameof(data)); + } + + Data = data; + } + /// /// Gets the size of the message in bytes. /// @@ -42,20 +56,6 @@ namespace Renci.SshNet.Messages.Transport } } - /// - /// Initializes a new instance of the class. - /// - /// The data. - public IgnoreMessage(byte[] data) - { - if (data is null) - { - throw new ArgumentNullException(nameof(data)); - } - - Data = data; - } - /// /// Called when type specific data need to be loaded. /// diff --git a/src/Renci.SshNet/Messages/Transport/KeyExchangeDhGroupExchangeGroup.cs b/src/Renci.SshNet/Messages/Transport/KeyExchangeDhGroupExchangeGroup.cs index 6fe1c76f..f1010a19 100644 --- a/src/Renci.SshNet/Messages/Transport/KeyExchangeDhGroupExchangeGroup.cs +++ b/src/Renci.SshNet/Messages/Transport/KeyExchangeDhGroupExchangeGroup.cs @@ -14,7 +14,7 @@ namespace Renci.SshNet.Messages.Transport private byte[] _subGroup; /// - /// Gets or sets the safe prime. + /// Gets the safe prime. /// /// /// The safe prime. @@ -25,7 +25,7 @@ namespace Renci.SshNet.Messages.Transport } /// - /// Gets or sets the generator for subgroup in GF(p). + /// Gets the generator for subgroup in GF(p). /// /// /// The sub group. diff --git a/src/Renci.SshNet/Messages/Transport/KeyExchangeDhReplyMessage.cs b/src/Renci.SshNet/Messages/Transport/KeyExchangeDhReplyMessage.cs index 105e5797..8f0a1489 100644 --- a/src/Renci.SshNet/Messages/Transport/KeyExchangeDhReplyMessage.cs +++ b/src/Renci.SshNet/Messages/Transport/KeyExchangeDhReplyMessage.cs @@ -7,7 +7,7 @@ public class KeyExchangeDhReplyMessage : Message { /// - /// Gets server public host key and certificates + /// Gets server public host key and certificates. /// /// The host key. public byte[] HostKey { get; private set; } diff --git a/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhInitMessage.cs b/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhInitMessage.cs index 4bd508f5..2d02d148 100644 --- a/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhInitMessage.cs +++ b/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhInitMessage.cs @@ -10,7 +10,7 @@ namespace Renci.SshNet.Messages.Transport internal sealed class KeyExchangeEcdhInitMessage : Message, IKeyExchangedAllowed { /// - /// Gets the client's ephemeral contribution to the ECDH exchange, encoded as an octet string + /// Gets the client's ephemeral contribution to the ECDH exchange, encoded as an octet string. /// public byte[] QC { get; private set; } diff --git a/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhReplyMessage.cs b/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhReplyMessage.cs index a194caf1..a5626aec 100644 --- a/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhReplyMessage.cs +++ b/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhReplyMessage.cs @@ -7,7 +7,7 @@ public class KeyExchangeEcdhReplyMessage : Message { /// - /// Gets a string encoding an X.509v3 certificate containing the server's ECDSA public host key + /// Gets a string encoding an X.509v3 certificate containing the server's ECDSA public host key. /// /// The host key. public byte[] KS { get; private set; } @@ -69,4 +69,4 @@ session.OnKeyExchangeEcdhReplyMessageReceived(this); } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet/Netconf/INetConfSession.cs b/src/Renci.SshNet/Netconf/INetConfSession.cs index 4dd3e24d..223cd3e0 100644 --- a/src/Renci.SshNet/Netconf/INetConfSession.cs +++ b/src/Renci.SshNet/Netconf/INetConfSession.cs @@ -1,25 +1,39 @@ using System.Xml; +using Renci.SshNet.Common; + namespace Renci.SshNet.NetConf { + /// + /// Represents a NETCONF session. + /// internal interface INetConfSession : ISubsystemSession { /// - /// Gets the NetConf server capabilities. + /// Gets the NETCONF server capabilities. /// /// - /// The NetConf server capabilities. + /// The NETCONF server capabilities. /// XmlDocument ServerCapabilities { get; } /// - /// Gets the NetConf client capabilities. + /// Gets the NETCONF client capabilities. /// /// - /// The NetConf client capabilities. + /// The NETCONF client capabilities. /// XmlDocument ClientCapabilities { get; } + /// + /// Sends the specified RPC request and returns the reply sent by the NETCONF server. + /// + /// The RPC request. + /// to automatically increment the message id and verify the message id of the RPC reply. + /// + /// The RPC reply. + /// + /// is and the message id in the RPC reply does not match the message id of the RPC request. XmlDocument SendReceiveRpc(XmlDocument rpc, bool automaticMessageIdHandling); } } diff --git a/src/Renci.SshNet/Netconf/NetConfSession.cs b/src/Renci.SshNet/Netconf/NetConfSession.cs index f1252f53..317b99c1 100644 --- a/src/Renci.SshNet/Netconf/NetConfSession.cs +++ b/src/Renci.SshNet/Netconf/NetConfSession.cs @@ -156,7 +156,11 @@ namespace Renci.SshNet.NetConf position += match.Index + match.Length + fractionLength; } +#if NET7_0_OR_GREATER + if (Regex.IsMatch(chunk.AsSpan(position), @"\n##\n")) +#else if (Regex.IsMatch(chunk.Substring(position), @"\n##\n")) +#endif // NET7_0_OR_GREATER { _ = _rpcReplyReceived.Set(); } diff --git a/src/Renci.SshNet/PasswordAuthenticationMethod.cs b/src/Renci.SshNet/PasswordAuthenticationMethod.cs index d6e6a78a..f5301624 100644 --- a/src/Renci.SshNet/PasswordAuthenticationMethod.cs +++ b/src/Renci.SshNet/PasswordAuthenticationMethod.cs @@ -105,7 +105,7 @@ namespace Renci.SshNet session.SendMessage(_requestMessage); session.WaitOnHandle(_authenticationCompleted); } - finally + finally { session.UnRegisterMessage("SSH_MSG_USERAUTH_PASSWD_CHANGEREQ"); session.UserAuthenticationSuccessReceived -= Session_UserAuthenticationSuccessReceived; diff --git a/src/Renci.SshNet/PasswordConnectionInfo.cs b/src/Renci.SshNet/PasswordConnectionInfo.cs index eb2ad923..08ef59de 100644 --- a/src/Renci.SshNet/PasswordConnectionInfo.cs +++ b/src/Renci.SshNet/PasswordConnectionInfo.cs @@ -6,7 +6,7 @@ using Renci.SshNet.Common; namespace Renci.SshNet { /// - /// Provides connection information when password authentication method is used + /// Provides connection information when password authentication method is used. /// /// /// @@ -260,7 +260,9 @@ namespace Renci.SshNet private void AuthenticationMethod_PasswordExpired(object sender, AuthenticationPasswordChangeEventArgs e) { +#pragma warning disable MA0091 // Sender should be 'this' for instance events PasswordExpired?.Invoke(sender, e); +#pragma warning restore MA0091 // Sender should be 'this' for instance events } /// diff --git a/src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs b/src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs index 7acf40ce..a8f7f4bd 100644 --- a/src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs +++ b/src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs @@ -17,7 +17,9 @@ namespace Renci.SshNet { private AuthenticationResult _authenticationResult = AuthenticationResult.Failure; private EventWaitHandle _authenticationCompleted = new ManualResetEvent(initialState: false); +#pragma warning disable S1450 // Private fields only used as local variables in methods should become local variables private bool _isSignatureRequired; +#pragma warning restore S1450 // Private fields only used as local variables in methods should become local variables private bool _isDisposed; /// @@ -168,7 +170,7 @@ namespace Renci.SshNet } /// - /// Releases unmanaged and - optionally - managed resources + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) diff --git a/src/Renci.SshNet/PrivateKeyFile.cs b/src/Renci.SshNet/PrivateKeyFile.cs index e5a3ffa3..eb7b3767 100644 --- a/src/Renci.SshNet/PrivateKeyFile.cs +++ b/src/Renci.SshNet/PrivateKeyFile.cs @@ -68,14 +68,14 @@ namespace Renci.SshNet public 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)+)-+ *END \k PRIVATE KEY *-+", - RegexOptions.Compiled | RegexOptions.Multiline); + RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture); private readonly List _hostAlgorithms = new List(); private Key _key; private bool _isDisposed; /// - /// The supported host algorithms for this key file. + /// Gets the supported host algorithms for this key file. /// public IReadOnlyCollection HostKeyAlgorithms { @@ -235,7 +235,7 @@ namespace Renci.SshNet cipher = new CipherInfo(256, (key, iv) => new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding())); break; default: - throw new SshException(string.Format(CultureInfo.CurrentCulture, "Private key cipher \"{0}\" is not supported.", cipherName)); + throw new SshException(string.Format(CultureInfo.InvariantCulture, "Private key cipher \"{0}\" is not supported.", cipherName)); } decryptedData = DecryptKey(cipher, binaryData, passPhrase, binarySalt); @@ -250,8 +250,10 @@ namespace Renci.SshNet case "RSA": var rsaKey = new RsaKey(decryptedData); _key = rsaKey; +#pragma warning disable CA2000 // Dispose objects before losing scope _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA512))); _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA256))); +#pragma warning restore CA2000 // Dispose objects before losing scope _hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key)); break; case "DSA": @@ -266,14 +268,17 @@ namespace Renci.SshNet _key = ParseOpenSshV1Key(decryptedData, passPhrase); if (_key is RsaKey parsedRsaKey) { +#pragma warning disable CA2000 // Dispose objects before losing scope _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(parsedRsaKey, HashAlgorithmName.SHA512))); _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(parsedRsaKey, HashAlgorithmName.SHA256))); +#pragma warning restore CA2000 // Dispose objects before losing scope _hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key)); } else { _hostAlgorithms.Add(new KeyHostAlgorithm(_key.ToString(), _key)); } + break; case "SSH2 ENCRYPTED": var reader = new SshDataReader(decryptedData); @@ -309,7 +314,9 @@ namespace Renci.SshNet throw new SshException(string.Format("Cipher method '{0}' is not supported.", cipherName)); } - // TODO: Create two specific data types to avoid using SshDataReader class + /* + * TODO: Create two specific data types to avoid using SshDataReader class. + */ reader = new SshDataReader(keyData); @@ -330,8 +337,10 @@ namespace Renci.SshNet var p = reader.ReadBigIntWithBits(); // q var decryptedRsaKey = new RsaKey(modulus, exponent, d, p, q, inverseQ); _key = decryptedRsaKey; +#pragma warning disable CA2000 // Dispose objects before losing scope _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(decryptedRsaKey, HashAlgorithmName.SHA512))); _hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(decryptedRsaKey, HashAlgorithmName.SHA256))); +#pragma warning restore CA2000 // Dispose objects before losing scope _hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key)); } else if (keyType == "dl-modp{sign{dsa-nist-sha1},dh{plain}}") @@ -341,6 +350,7 @@ namespace Renci.SshNet { throw new SshException("Invalid private key"); } + var p = reader.ReadBigIntWithBits(); var g = reader.ReadBigIntWithBits(); var q = reader.ReadBigIntWithBits(); @@ -353,6 +363,7 @@ namespace Renci.SshNet { throw new NotSupportedException(string.Format("Key type '{0}' is not supported.", keyType)); } + break; default: throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supported.", keyName)); @@ -436,8 +447,8 @@ namespace Renci.SshNet /// Parses an OpenSSH V1 key file (i.e. ED25519 key) according to the the key spec: /// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key. /// - /// the key file data (i.e. base64 encoded data between the header/footer) - /// passphrase or null if there isn't one + /// The key file data (i.e. base64 encoded data between the header/footer). + /// Passphrase or if there isn't one. /// /// The OpenSSH V1 key. /// @@ -540,7 +551,10 @@ namespace Renci.SshNet var checkInt2 = (int) privateKeyReader.ReadUInt32(); if (checkInt1 != checkInt2) { - throw new SshException("The random check bytes of the OpenSSH key do not match (" + checkInt1 + " <->" + checkInt2 + ")."); + throw new SshException(string.Format(CultureInfo.InvariantCulture, + "The random check bytes of the OpenSSH key do not match ({0} <-> {1}).", + checkInt1.ToString(CultureInfo.InvariantCulture), + checkInt2.ToString(CultureInfo.InvariantCulture))); } // key type @@ -595,7 +609,8 @@ namespace Renci.SshNet { if ((int) padding[i] != i + 1) { - throw new SshException("Padding of openssh key format contained wrong byte at position: " + i); + throw new SshException("Padding of openssh key format contained wrong byte at position: " + + i.ToString(CultureInfo.InvariantCulture)); } } diff --git a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs index d99338fe..7af4c638 100644 --- a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs +++ b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs @@ -14,12 +14,11 @@ using System.Runtime.InteropServices; [assembly: AssemblyInformationalVersion("2023.0.0")] [assembly: CLSCompliant(false)] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] - #if DEBUG [assembly: AssemblyConfiguration("Debug")] #else diff --git a/src/Renci.SshNet/ProxyTypes.cs b/src/Renci.SshNet/ProxyTypes.cs index 9a7fce09..cff5eb24 100644 --- a/src/Renci.SshNet/ProxyTypes.cs +++ b/src/Renci.SshNet/ProxyTypes.cs @@ -5,13 +5,24 @@ /// public enum ProxyTypes { - /// No proxy server. + /// + /// No proxy server. + /// None, - /// A SOCKS4 proxy server. + + /// + /// A SOCKS4 proxy server. + /// Socks4, - /// A SOCKS5 proxy server. + + /// + /// A SOCKS5 proxy server. + /// Socks5, - /// A HTTP proxy server. + + /// + /// An HTTP proxy server. + /// Http, } } diff --git a/src/Renci.SshNet/RemotePathTransformation.cs b/src/Renci.SshNet/RemotePathTransformation.cs index 5e3f74fc..7404c3e4 100644 --- a/src/Renci.SshNet/RemotePathTransformation.cs +++ b/src/Renci.SshNet/RemotePathTransformation.cs @@ -27,7 +27,7 @@ private static readonly IRemotePathTransformation DoubleQuoteTransformation = new RemotePathDoubleQuoteTransformation(); /// - /// Quotes a path in a way to be suitable to be used with a shell-based server. + /// Gets a that quotes a path in a way to be suitable to be used with a shell-based server. /// /// /// A quoted path. @@ -85,7 +85,7 @@ } /// - /// Performs no transformation. + /// Gets a that performs no transformation. /// /// /// Recommended for servers that do not require any character to be escaped or enclosed in quotes, @@ -97,7 +97,7 @@ } /// - /// Encloses a path in double quotes, and escapes any embedded double quote with a backslash. + /// Gets a that encloses a path in double quotes, and escapes any embedded double quote with a backslash. /// /// /// A transformation that encloses a path in double quotes, and escapes any embedded double quote with diff --git a/src/Renci.SshNet/ScpClient.cs b/src/Renci.SshNet/ScpClient.cs index 1413a12d..8121c970 100644 --- a/src/Renci.SshNet/ScpClient.cs +++ b/src/Renci.SshNet/ScpClient.cs @@ -632,7 +632,11 @@ namespace Renci.SshNet /// The file or directory to upload. private void UploadTimes(IChannelSession channel, Stream input, FileSystemInfo fileOrDirectory) { +#if NET ||NETSTANDARD2_1_OR_GREATER + var zeroTime = DateTime.UnixEpoch; +#else var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); +#endif var modificationSeconds = (long) (fileOrDirectory.LastWriteTimeUtc - zeroTime).TotalSeconds; var accessSeconds = (long) (fileOrDirectory.LastAccessTimeUtc - zeroTime).TotalSeconds; SendData(channel, string.Format(CultureInfo.InvariantCulture, "T{0} 0 {1} 0\n", modificationSeconds, accessSeconds)); @@ -805,7 +809,11 @@ namespace Renci.SshNet var mtime = long.Parse(match.Result("${mtime}"), CultureInfo.InvariantCulture); var atime = long.Parse(match.Result("${atime}"), CultureInfo.InvariantCulture); +#if NET || NETSTANDARD2_1_OR_GREATER + var zeroTime = DateTime.UnixEpoch; +#else var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); +#endif modifiedTime = zeroTime.AddSeconds(mtime); accessedTime = zeroTime.AddSeconds(atime); continue; diff --git a/src/Renci.SshNet/Security/BouncyCastle/math/BigInteger.cs b/src/Renci.SshNet/Security/BouncyCastle/math/BigInteger.cs index 635a2540..a98959ef 100644 --- a/src/Renci.SshNet/Security/BouncyCastle/math/BigInteger.cs +++ b/src/Renci.SshNet/Security/BouncyCastle/math/BigInteger.cs @@ -648,7 +648,9 @@ namespace Renci.SshNet.Security.Org.BouncyCastle.Math int nBytes = GetByteLength(sizeInBits); byte[] b = new byte[nBytes]; +#pragma warning disable CA5394 // Do not use insecure randomness random.NextBytes(b); +#pragma warning restore CA5394 // Do not use insecure randomness // strip off any excess bits in the MSB int xBits = BitsPerByte * nBytes - sizeInBits; @@ -658,6 +660,7 @@ namespace Renci.SshNet.Security.Org.BouncyCastle.Math this.sign = this.magnitude.Length < 1 ? 0 : 1; } +#pragma warning disable CA5394 // Do not use insecure randomness public BigInteger( int bitLength, int certainty, @@ -716,6 +719,7 @@ namespace Renci.SshNet.Security.Org.BouncyCastle.Math } } } +#pragma warning restore CA5394 // Do not use insecure randomness public BigInteger Abs() { diff --git a/src/Renci.SshNet/Security/BouncyCastle/security/SecureRandom.cs b/src/Renci.SshNet/Security/BouncyCastle/security/SecureRandom.cs index 377817e0..6e86c47c 100644 --- a/src/Renci.SshNet/Security/BouncyCastle/security/SecureRandom.cs +++ b/src/Renci.SshNet/Security/BouncyCastle/security/SecureRandom.cs @@ -58,7 +58,9 @@ namespace Renci.SshNet.Security.Org.BouncyCastle.Security /// /// The source to generate all random bytes from. public SecureRandom(IRandomGenerator generator) +#pragma warning disable CA5394 // Do not use insecure randomness : base(0) +#pragma warning restore CA5394 // Do not use insecure randomness { this.generator = generator; } diff --git a/src/Renci.SshNet/Security/CertificateHostAlgorithm.cs b/src/Renci.SshNet/Security/CertificateHostAlgorithm.cs index 6110e321..41d5f410 100644 --- a/src/Renci.SshNet/Security/CertificateHostAlgorithm.cs +++ b/src/Renci.SshNet/Security/CertificateHostAlgorithm.cs @@ -29,7 +29,7 @@ namespace Renci.SshNet.Security /// /// The data. /// Signed data. - /// + /// Always. public override byte[] Sign(byte[] data) { throw new NotImplementedException(); @@ -41,7 +41,7 @@ namespace Renci.SshNet.Security /// The data. /// The signature. /// if signature was successfully verified; otherwise . - /// + /// Always. public override bool VerifySignature(byte[] data, byte[] signature) { throw new NotImplementedException(); diff --git a/src/Renci.SshNet/Security/Cryptography/AsymmetricCipher.cs b/src/Renci.SshNet/Security/Cryptography/AsymmetricCipher.cs index 91d0c77f..74e4a733 100644 --- a/src/Renci.SshNet/Security/Cryptography/AsymmetricCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/AsymmetricCipher.cs @@ -3,7 +3,7 @@ /// /// Base class for asymmetric cipher implementations. /// - public abstract class AsymmetricCipher : Cipher + public abstract class AsymmetricCipher : Cipher { /// /// Gets the minimum data size. diff --git a/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs b/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs index 00c66f6c..b9f7dde5 100644 --- a/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/BlockCipher.cs @@ -69,7 +69,9 @@ namespace Renci.SshNet.Security.Cryptography /// The data. /// The zero-based offset in at which to begin encrypting. /// The number of bytes to encrypt from . - /// Encrypted data + /// + /// The encrypted data. + /// public override byte[] Encrypt(byte[] input, int offset, int length) { if (length % _blockSize > 0) @@ -112,7 +114,9 @@ namespace Renci.SshNet.Security.Cryptography /// Decrypts the specified data. /// /// The data. - /// Decrypted data + /// + /// The decrypted data. + /// public override byte[] Decrypt(byte[] input) { return Decrypt(input, 0, input.Length); diff --git a/src/Renci.SshNet/Security/Cryptography/CipherDigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/CipherDigitalSignature.cs index ed37e434..e61b5d65 100644 --- a/src/Renci.SshNet/Security/Cryptography/CipherDigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/CipherDigitalSignature.cs @@ -4,7 +4,7 @@ using Renci.SshNet.Common; namespace Renci.SshNet.Security.Cryptography { /// - /// Implements digital signature where where asymmetric cipher is used, + /// Implements digital signature where where asymmetric cipher is used. /// public abstract class CipherDigitalSignature : DigitalSignature { @@ -33,7 +33,7 @@ namespace Renci.SshNet.Security.Cryptography /// The input. /// The signature. /// - /// if signature was successfully verified; otherwise . + /// if signature was successfully verified; otherwise . /// public override bool Verify(byte[] input, byte[] signature) { diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs index 09e8ab90..517f2b46 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs @@ -17,7 +17,10 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers private int _rounds; private uint[] _encryptionKey; private uint[] _decryptionKey; - private uint _c0, _c1, _c2, _c3; + private uint _c0; + private uint _c1; + private uint _c2; + private uint _c3; #region Static Definition Tables @@ -583,7 +586,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// The number of bytes encrypted. /// /// or is . - /// or is too short. + /// or is too short. public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { if (inputBuffer is null) @@ -598,12 +601,12 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers if ((inputOffset + (32 / 2)) > inputBuffer.Length) { - throw new IndexOutOfRangeException("input buffer too short"); + throw new ArgumentException("input buffer too short"); } if ((outputOffset + (32 / 2)) > outputBuffer.Length) { - throw new IndexOutOfRangeException("output buffer too short"); + throw new ArgumentException("output buffer too short"); } _encryptionKey ??= GenerateWorkingKey(isEncryption: true, Key); @@ -644,12 +647,12 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers if ((inputOffset + (32 / 2)) > inputBuffer.Length) { - throw new IndexOutOfRangeException("input buffer too short"); + throw new ArgumentException("input buffer too short"); } if ((outputOffset + (32 / 2)) > outputBuffer.Length) { - throw new IndexOutOfRangeException("output buffer too short"); + throw new ArgumentException("output buffer too short"); } _decryptionKey ??= GenerateWorkingKey(isEncryption: false, Key); @@ -675,26 +678,26 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers _rounds = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes var W = new uint[(_rounds + 1) * 4]; // 4 words in a block - // - // copy the key into the round key array - // + /* + * Copy the key into the round key array. + */ var t = 0; for (var i = 0; i < key.Length; t++) { - W[(t >> 2) * 4 + (t & 3)] = Pack.LittleEndianToUInt32(key, i); + W[((t >> 2) * 4) + (t & 3)] = Pack.LittleEndianToUInt32(key, i); i += 4; } - // - // while not enough round key material calculated - // calculate new values - // + /* + * Calculate new values while not enough round key material is calculated. + */ + var k = (_rounds + 1) << 2; for (var i = KC; i < k; i++) { - var temp = W[((i - 1) >> 2) * 4 + ((i - 1) & 3)]; + var temp = W[(((i - 1) >> 2) * 4) + ((i - 1) & 3)]; if ((i % KC) == 0) { temp = SubWord(Shift(temp, 8)) ^ Rcon[(i / KC) - 1]; @@ -704,7 +707,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers temp = SubWord(temp); } - W[(i >> 2) * 4 + (i & 3)] = W[((i - KC) >> 2) * 4 + ((i - KC) & 3)] ^ temp; + W[((i >> 2) * 4) + (i & 3)] = W[(((i - KC) >> 2) * 4) + ((i - KC) & 3)] ^ temp; } if (!isEncryption) @@ -713,7 +716,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers { for (var i = 0; i < 4; i++) { - W[j * 4 + i] = InvMcol(W[j * 4 + i]); + W[(j * 4) + i] = InvMcol(W[(j * 4) + i]); } } } @@ -765,74 +768,82 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers Pack.UInt32ToLittleEndian(_c3, bytes, off + 12); } +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter private void EncryptBlock(uint[] KW) +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter { int r; uint r0, r1, r2, r3; - _c0 ^= KW[0 * 4 + 0]; - _c1 ^= KW[0 * 4 + 1]; - _c2 ^= KW[0 * 4 + 2]; - _c3 ^= KW[0 * 4 + 3]; + _c0 ^= KW[(0 * 4) + 0]; + _c1 ^= KW[(0 * 4) + 1]; + _c2 ^= KW[(0 * 4) + 2]; + _c3 ^= KW[(0 * 4) + 3]; for (r = 1; r < _rounds - 1;) { - r0 = T0[_c0 & 255] ^ T1[(_c1 >> 8) & 255] ^ T2[(_c2 >> 16) & 255] ^ T3[_c3 >> 24] ^ KW[r * 4 + 0]; - r1 = T0[_c1 & 255] ^ T1[(_c2 >> 8) & 255] ^ T2[(_c3 >> 16) & 255] ^ T3[_c0 >> 24] ^ KW[r * 4 + 1]; - r2 = T0[_c2 & 255] ^ T1[(_c3 >> 8) & 255] ^ T2[(_c0 >> 16) & 255] ^ T3[_c1 >> 24] ^ KW[r * 4 + 2]; - r3 = T0[_c3 & 255] ^ T1[(_c0 >> 8) & 255] ^ T2[(_c1 >> 16) & 255] ^ T3[_c2 >> 24] ^ KW[r++ * 4 + 3]; - _c0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[r3 >> 24] ^ KW[r * 4 + 0]; - _c1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[r0 >> 24] ^ KW[r * 4 + 1]; - _c2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[r1 >> 24] ^ KW[r * 4 + 2]; - _c3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[r2 >> 24] ^ KW[r++ * 4 + 3]; + r0 = T0[_c0 & 255] ^ T1[(_c1 >> 8) & 255] ^ T2[(_c2 >> 16) & 255] ^ T3[_c3 >> 24] ^ KW[(r * 4) + 0]; + r1 = T0[_c1 & 255] ^ T1[(_c2 >> 8) & 255] ^ T2[(_c3 >> 16) & 255] ^ T3[_c0 >> 24] ^ KW[(r * 4) + 1]; + r2 = T0[_c2 & 255] ^ T1[(_c3 >> 8) & 255] ^ T2[(_c0 >> 16) & 255] ^ T3[_c1 >> 24] ^ KW[(r * 4) + 2]; + r3 = T0[_c3 & 255] ^ T1[(_c0 >> 8) & 255] ^ T2[(_c1 >> 16) & 255] ^ T3[_c2 >> 24] ^ KW[(r++ * 4) + 3]; + _c0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[r3 >> 24] ^ KW[(r * 4) + 0]; + _c1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[r0 >> 24] ^ KW[(r * 4) + 1]; + _c2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[r1 >> 24] ^ KW[(r * 4) + 2]; + _c3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[r2 >> 24] ^ KW[(r++ * 4) + 3]; } - r0 = T0[_c0 & 255] ^ T1[(_c1 >> 8) & 255] ^ T2[(_c2 >> 16) & 255] ^ T3[_c3 >> 24] ^ KW[r * 4 + 0]; - r1 = T0[_c1 & 255] ^ T1[(_c2 >> 8) & 255] ^ T2[(_c3 >> 16) & 255] ^ T3[_c0 >> 24] ^ KW[r * 4 + 1]; - r2 = T0[_c2 & 255] ^ T1[(_c3 >> 8) & 255] ^ T2[(_c0 >> 16) & 255] ^ T3[_c1 >> 24] ^ KW[r * 4 + 2]; - r3 = T0[_c3 & 255] ^ T1[(_c0 >> 8) & 255] ^ T2[(_c1 >> 16) & 255] ^ T3[_c2 >> 24] ^ KW[r++ * 4 + 3]; + r0 = T0[_c0 & 255] ^ T1[(_c1 >> 8) & 255] ^ T2[(_c2 >> 16) & 255] ^ T3[_c3 >> 24] ^ KW[(r * 4) + 0]; + r1 = T0[_c1 & 255] ^ T1[(_c2 >> 8) & 255] ^ T2[(_c3 >> 16) & 255] ^ T3[_c0 >> 24] ^ KW[(r * 4) + 1]; + r2 = T0[_c2 & 255] ^ T1[(_c3 >> 8) & 255] ^ T2[(_c0 >> 16) & 255] ^ T3[_c1 >> 24] ^ KW[(r * 4) + 2]; + r3 = T0[_c3 & 255] ^ T1[(_c0 >> 8) & 255] ^ T2[(_c1 >> 16) & 255] ^ T3[_c2 >> 24] ^ KW[(r++ * 4) + 3]; - // the final round's table is a simple function of S so we don't use a whole other four tables for it + /* + * The final round's table is a simple function of S so we don't use a whole other four tables for it. + */ - _c0 = (uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)S[(r2 >> 16) & 255]) << 16) ^ (((uint)S[r3 >> 24]) << 24) ^ KW[r * 4 + 0]; - _c1 = (uint)S[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[r0 >> 24]) << 24) ^ KW[r * 4 + 1]; - _c2 = (uint)S[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[r1 >> 24]) << 24) ^ KW[r * 4 + 2]; - _c3 = (uint)S[r3 & 255] ^ (((uint)S[(r0 >> 8) & 255]) << 8) ^ (((uint)S[(r1 >> 16) & 255]) << 16) ^ (((uint)S[r2 >> 24]) << 24) ^ KW[r * 4 + 3]; + _c0 = (uint)S[r0 & 255] ^ (((uint)S[(r1 >> 8) & 255]) << 8) ^ (((uint)S[(r2 >> 16) & 255]) << 16) ^ (((uint)S[r3 >> 24]) << 24) ^ KW[(r * 4) + 0]; + _c1 = (uint)S[r1 & 255] ^ (((uint)S[(r2 >> 8) & 255]) << 8) ^ (((uint)S[(r3 >> 16) & 255]) << 16) ^ (((uint)S[r0 >> 24]) << 24) ^ KW[(r * 4) + 1]; + _c2 = (uint)S[r2 & 255] ^ (((uint)S[(r3 >> 8) & 255]) << 8) ^ (((uint)S[(r0 >> 16) & 255]) << 16) ^ (((uint)S[r1 >> 24]) << 24) ^ KW[(r * 4) + 2]; + _c3 = (uint)S[r3 & 255] ^ (((uint)S[(r0 >> 8) & 255]) << 8) ^ (((uint)S[(r1 >> 16) & 255]) << 16) ^ (((uint)S[r2 >> 24]) << 24) ^ KW[(r * 4) + 3]; } +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter private void DecryptBlock(uint[] KW) +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter { int r; uint r0, r1, r2, r3; - _c0 ^= KW[_rounds * 4 + 0]; - _c1 ^= KW[_rounds * 4 + 1]; - _c2 ^= KW[_rounds * 4 + 2]; - _c3 ^= KW[_rounds * 4 + 3]; + _c0 ^= KW[(_rounds * 4) + 0]; + _c1 ^= KW[(_rounds * 4) + 1]; + _c2 ^= KW[(_rounds * 4) + 2]; + _c3 ^= KW[(_rounds * 4) + 3]; for (r = _rounds - 1; r > 1;) { - r0 = Tinv0[_c0 & 255] ^ Tinv1[(_c3 >> 8) & 255] ^ Tinv2[(_c2 >> 16) & 255] ^ Tinv3[_c1 >> 24] ^ KW[r * 4 + 0]; - r1 = Tinv0[_c1 & 255] ^ Tinv1[(_c0 >> 8) & 255] ^ Tinv2[(_c3 >> 16) & 255] ^ Tinv3[_c2 >> 24] ^ KW[r * 4 + 1]; - r2 = Tinv0[_c2 & 255] ^ Tinv1[(_c1 >> 8) & 255] ^ Tinv2[(_c0 >> 16) & 255] ^ Tinv3[_c3 >> 24] ^ KW[r * 4 + 2]; - r3 = Tinv0[_c3 & 255] ^ Tinv1[(_c2 >> 8) & 255] ^ Tinv2[(_c1 >> 16) & 255] ^ Tinv3[_c0 >> 24] ^ KW[r-- * 4 + 3]; - _c0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[r1 >> 24] ^ KW[r * 4 + 0]; - _c1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[r2 >> 24] ^ KW[r * 4 + 1]; - _c2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[r3 >> 24] ^ KW[r * 4 + 2]; - _c3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[r0 >> 24] ^ KW[r-- * 4 + 3]; + r0 = Tinv0[_c0 & 255] ^ Tinv1[(_c3 >> 8) & 255] ^ Tinv2[(_c2 >> 16) & 255] ^ Tinv3[_c1 >> 24] ^ KW[(r * 4) + 0]; + r1 = Tinv0[_c1 & 255] ^ Tinv1[(_c0 >> 8) & 255] ^ Tinv2[(_c3 >> 16) & 255] ^ Tinv3[_c2 >> 24] ^ KW[(r * 4) + 1]; + r2 = Tinv0[_c2 & 255] ^ Tinv1[(_c1 >> 8) & 255] ^ Tinv2[(_c0 >> 16) & 255] ^ Tinv3[_c3 >> 24] ^ KW[(r * 4) + 2]; + r3 = Tinv0[_c3 & 255] ^ Tinv1[(_c2 >> 8) & 255] ^ Tinv2[(_c1 >> 16) & 255] ^ Tinv3[_c0 >> 24] ^ KW[(r-- * 4) + 3]; + _c0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[r1 >> 24] ^ KW[(r * 4) + 0]; + _c1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[r2 >> 24] ^ KW[(r * 4) + 1]; + _c2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[r3 >> 24] ^ KW[(r * 4) + 2]; + _c3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[r0 >> 24] ^ KW[(r-- * 4) + 3]; } - r0 = Tinv0[_c0 & 255] ^ Tinv1[(_c3 >> 8) & 255] ^ Tinv2[(_c2 >> 16) & 255] ^ Tinv3[_c1 >> 24] ^ KW[r * 4 + 0]; - r1 = Tinv0[_c1 & 255] ^ Tinv1[(_c0 >> 8) & 255] ^ Tinv2[(_c3 >> 16) & 255] ^ Tinv3[_c2 >> 24] ^ KW[r * 4 + 1]; - r2 = Tinv0[_c2 & 255] ^ Tinv1[(_c1 >> 8) & 255] ^ Tinv2[(_c0 >> 16) & 255] ^ Tinv3[_c3 >> 24] ^ KW[r * 4 + 2]; - r3 = Tinv0[_c3 & 255] ^ Tinv1[(_c2 >> 8) & 255] ^ Tinv2[(_c1 >> 16) & 255] ^ Tinv3[_c0 >> 24] ^ KW[r * 4 + 3]; + r0 = Tinv0[_c0 & 255] ^ Tinv1[(_c3 >> 8) & 255] ^ Tinv2[(_c2 >> 16) & 255] ^ Tinv3[_c1 >> 24] ^ KW[(r * 4) + 0]; + r1 = Tinv0[_c1 & 255] ^ Tinv1[(_c0 >> 8) & 255] ^ Tinv2[(_c3 >> 16) & 255] ^ Tinv3[_c2 >> 24] ^ KW[(r * 4) + 1]; + r2 = Tinv0[_c2 & 255] ^ Tinv1[(_c1 >> 8) & 255] ^ Tinv2[(_c0 >> 16) & 255] ^ Tinv3[_c3 >> 24] ^ KW[(r * 4) + 2]; + r3 = Tinv0[_c3 & 255] ^ Tinv1[(_c2 >> 8) & 255] ^ Tinv2[(_c1 >> 16) & 255] ^ Tinv3[_c0 >> 24] ^ KW[(r * 4) + 3]; - // the final round's table is a simple function of Si so we don't use a whole other four tables for it + /* + * The final round's table is a simple function of Si so we don't use a whole other four tables for it. + */ - _c0 = (uint)Si[r0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(r2 >> 16) & 255]) << 16) ^ (((uint)Si[r1 >> 24]) << 24) ^ KW[0 * 4 + 0]; - _c1 = (uint)Si[r1 & 255] ^ (((uint)Si[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ (((uint)Si[r2 >> 24]) << 24) ^ KW[0 * 4 + 1]; - _c2 = (uint)Si[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ (((uint)Si[r3 >> 24]) << 24) ^ KW[0 * 4 + 2]; - _c3 = (uint)Si[r3 & 255] ^ (((uint)Si[(r2 >> 8) & 255]) << 8) ^ (((uint)Si[(r1 >> 16) & 255]) << 16) ^ (((uint)Si[r0 >> 24]) << 24) ^ KW[0 * 4 + 3]; + _c0 = (uint)Si[r0 & 255] ^ (((uint)Si[(r3 >> 8) & 255]) << 8) ^ (((uint)Si[(r2 >> 16) & 255]) << 16) ^ (((uint)Si[r1 >> 24]) << 24) ^ KW[(0 * 4) + 0]; + _c1 = (uint)Si[r1 & 255] ^ (((uint)Si[(r0 >> 8) & 255]) << 8) ^ (((uint)Si[(r3 >> 16) & 255]) << 16) ^ (((uint)Si[r2 >> 24]) << 24) ^ KW[(0 * 4) + 1]; + _c2 = (uint)Si[r2 & 255] ^ (((uint)Si[(r1 >> 8) & 255]) << 8) ^ (((uint)Si[(r0 >> 16) & 255]) << 16) ^ (((uint)Si[r3 >> 24]) << 24) ^ KW[(0 * 4) + 2]; + _c3 = (uint)Si[r3 & 255] ^ (((uint)Si[(r2 >> 8) & 255]) << 8) ^ (((uint)Si[(r1 >> 16) & 255]) << 16) ^ (((uint)Si[r0 >> 24]) << 24) ^ KW[(0 * 4) + 3]; } } } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/Arc4Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/Arc4Cipher.cs index 5bfbd011..41387ee0 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/Arc4Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/Arc4Cipher.cs @@ -3,14 +3,16 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers { /// - /// Implements ARCH4 cipher algorithm + /// Implements ARCH4 cipher algorithm. /// public sealed class Arc4Cipher : StreamCipher { - private static readonly int STATE_LENGTH = 256; +#pragma warning disable SA1310 // Field names should not contain underscore + private const int STATE_LENGTH = 256; +#pragma warning restore SA1310 // Field names should not contain underscore /// - /// Holds the state of the RC4 engine + /// Holds the state of the RC4 engine. /// private byte[] _engineState; @@ -119,21 +121,19 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// public override byte[] Decrypt(byte[] input, int offset, int length) { - var output = new byte[length]; - _ = ProcessBytes(input, offset, length, output, 0); - return output; + return Encrypt(input, offset, length); } private int ProcessBytes(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { if ((inputOffset + inputCount) > inputBuffer.Length) { - throw new IndexOutOfRangeException("input buffer too short"); + throw new ArgumentException("input buffer too short"); } if ((outputOffset + inputCount) > outputBuffer.Length) { - throw new IndexOutOfRangeException("output buffer too short"); + throw new ArgumentException("output buffer too short"); } for (var i = 0; i < inputCount; i++) diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/BlowfishCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/BlowfishCipher.cs index b2e08205..6e04c999 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/BlowfishCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/BlowfishCipher.cs @@ -8,6 +8,12 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// public sealed class BlowfishCipher : BlockCipher { + private const int Rounds = 16; + + private const int SboxSk = 256; + + private const int PSize = Rounds + 2; + #region Static reference tables private static readonly uint[] KP = @@ -293,19 +299,16 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers #endregion - private const int Rounds = 16; - - private const int SboxSk = 256; - - private const int PSize = Rounds + 2; - /// - /// The s-boxes + /// The s-boxes. /// - private readonly uint[] _s0, _s1, _s2, _s3; + private readonly uint[] _s0; + private readonly uint[] _s1; + private readonly uint[] _s2; + private readonly uint[] _s3; /// - /// The p-array + /// The p-array. /// private readonly uint[] _p; diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/CastCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/CastCipher.cs index fa114bfe..d2388785 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/CastCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/CastCipher.cs @@ -4,21 +4,21 @@ using Renci.SshNet.Common; namespace Renci.SshNet.Security.Cryptography.Ciphers { /// - /// Implements CAST cipher algorithm + /// Implements CAST cipher algorithm. /// public sealed class CastCipher : BlockCipher { - private static readonly int MaxRounds = 16; + private const int MaxRounds = 16; - private static readonly int RedRounds = 12; + private const int RedRounds = 12; /// - /// The rotating round key + /// The rotating round key. /// private readonly int[] _kr = new int[17]; /// - /// The masking round key + /// The masking round key. /// private readonly uint[] _km = new uint[17]; @@ -58,9 +58,11 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { - // process the input block - // batch the units up into a 32 bit chunk and go for it - // the array is in bytes, the increment is 8x8 bits = 64 + /* + * process the input block + * batch the units up into a 32 bit chunk and go for it + * the array is in bytes, the increment is 8x8 bits = 64 + */ var l0 = Pack.BigEndianToUInt32(inputBuffer, inputOffset); var r0 = Pack.BigEndianToUInt32(inputBuffer, inputOffset + 4); @@ -575,7 +577,6 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// The input to be processed. /// The mask to be used from Km[n]. /// The rotation value to be used. - /// private static uint F1(uint d, uint kmi, int kri) { var I = kmi + d; @@ -589,7 +590,6 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// The input to be processed. /// The mask to be used from Km[n]. /// The rotation value to be used. - /// private static uint F2(uint d, uint kmi, int kri) { var I = kmi ^ d; @@ -603,7 +603,6 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// The input to be processed. /// The mask to be used from Km[n]. /// The rotation value to be used. - /// private static uint F3(uint d, uint kmi, int kri) { var I = kmi - d; diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherMode.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherMode.cs index 7352a12f..490756af 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherMode.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherMode.cs @@ -3,10 +3,12 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers { /// - /// Base class for cipher mode implementations + /// Base class for cipher mode implementations. /// public abstract class CipherMode { +#pragma warning disable SA1401 // Fields should be private +#pragma warning disable SA1306 // Field names should begin with lower-case letter /// /// Gets the cipher. /// @@ -21,6 +23,8 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// Holds block size of the cipher. /// protected int _blockSize; +#pragma warning restore SA1306 // Field names should begin with lower-case letter +#pragma warning restore SA1401 // Fields should be private /// /// Initializes a new instance of the class. diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherPadding.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherPadding.cs index 5013865c..914babc1 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherPadding.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/CipherPadding.cs @@ -1,7 +1,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers { /// - /// Base class for cipher padding implementations + /// Base class for cipher padding implementations. /// public abstract class CipherPadding { diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/DesCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/DesCipher.cs index aac59353..f2d2e403 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/DesCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/DesCipher.cs @@ -12,7 +12,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers private int[] _decryptionKey; - private static readonly short[] Bytebit = {128, 64, 32, 16, 8, 4, 2, 1}; + private static readonly short[] Bytebit = { 128, 64, 32, 16, 8, 4, 2, 1 }; private static readonly int[] Bigbyte = { @@ -237,12 +237,12 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers { if ((inputOffset + BlockSize) > inputBuffer.Length) { - throw new IndexOutOfRangeException("input buffer too short"); + throw new ArgumentException("input buffer too short"); } if ((outputOffset + BlockSize) > outputBuffer.Length) { - throw new IndexOutOfRangeException("output buffer too short"); + throw new ArgumentException("output buffer too short"); } _encryptionKey ??= GenerateWorkingKey(encrypting: true, Key); @@ -267,12 +267,12 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers { if ((inputOffset + BlockSize) > inputBuffer.Length) { - throw new IndexOutOfRangeException("input buffer too short"); + throw new ArgumentException("input buffer too short"); } if ((outputOffset + BlockSize) > outputBuffer.Length) { - throw new IndexOutOfRangeException("output buffer too short"); + throw new ArgumentException("output buffer too short"); } _decryptionKey ??= GenerateWorkingKey(encrypting: false, Key); @@ -300,7 +300,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers { int l = Pc1[j]; - pc1m[j] = ((key[(uint)l >> 3] & Bytebit[l & 07]) != 0); + pc1m[j] = (key[(uint) l >> 3] & Bytebit[l & 07]) != 0; } for (var i = 0; i < 16; i++) @@ -359,9 +359,10 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers } } - // - // store the processed key - // + /* + * store the processed key + */ + for (var i = 0; i != 32; i += 2) { var i1 = newKey[i]; @@ -428,24 +429,24 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers for (var round = 0; round < 8; round++) { work = (right << 28) | (right >> 4); - work ^= (uint)wKey[round * 4 + 0]; + work ^= (uint)wKey[(round * 4) + 0]; var fval = Sp7[work & 0x3f]; fval |= Sp5[(work >> 8) & 0x3f]; fval |= Sp3[(work >> 16) & 0x3f]; fval |= Sp1[(work >> 24) & 0x3f]; - work = right ^ (uint) wKey[round * 4 + 1]; + work = right ^ (uint) wKey[(round * 4) + 1]; fval |= Sp8[work & 0x3f]; fval |= Sp6[(work >> 8) & 0x3f]; fval |= Sp4[(work >> 16) & 0x3f]; fval |= Sp2[(work >> 24) & 0x3f]; left ^= fval; work = (left << 28) | (left >> 4); - work ^= (uint)wKey[round * 4 + 2]; + work ^= (uint)wKey[(round * 4) + 2]; fval = Sp7[work & 0x3f]; fval |= Sp5[(work >> 8) & 0x3f]; fval |= Sp3[(work >> 16) & 0x3f]; fval |= Sp1[(work >> 24) & 0x3f]; - work = left ^ (uint)wKey[round * 4 + 3]; + work = left ^ (uint)wKey[(round * 4) + 3]; fval |= Sp8[work & 0x3f]; fval |= Sp6[(work >> 8) & 0x3f]; fval |= Sp4[(work >> 16) & 0x3f]; diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CbcCipherMode.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CbcCipherMode.cs index de07141c..a2b9243d 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CbcCipherMode.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CbcCipherMode.cs @@ -4,7 +4,7 @@ using System.Globalization; namespace Renci.SshNet.Security.Cryptography.Ciphers.Modes { /// - /// Implements CBC cipher mode + /// Implements CBC cipher mode. /// public class CbcCipherMode : CipherMode { diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CfbCipherMode.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CfbCipherMode.cs index ae889e87..23a4bb2f 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CfbCipherMode.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CfbCipherMode.cs @@ -4,7 +4,7 @@ using System.Globalization; namespace Renci.SshNet.Security.Cryptography.Ciphers.Modes { /// - /// Implements CFB cipher mode + /// Implements CFB cipher mode. /// public class CfbCipherMode : CipherMode { diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CtrCipherMode.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CtrCipherMode.cs index 90149f57..a0ae5010 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CtrCipherMode.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/CtrCipherMode.cs @@ -4,7 +4,7 @@ using System.Globalization; namespace Renci.SshNet.Security.Cryptography.Ciphers.Modes { /// - /// Implements CTR cipher mode + /// Implements CTR cipher mode. /// public class CtrCipherMode : CipherMode { @@ -77,35 +77,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers.Modes /// public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { - if (inputBuffer.Length - inputOffset < _blockSize) - { - throw new ArgumentException("Invalid input buffer"); - } - - if (outputBuffer.Length - outputOffset < _blockSize) - { - throw new ArgumentException("Invalid output buffer"); - } - - if (inputCount != _blockSize) - { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "inputCount must be {0}.", _blockSize)); - } - - _ = Cipher.EncryptBlock(IV, 0, IV.Length, _ivOutput, 0); - - for (var i = 0; i < _blockSize; i++) - { - outputBuffer[outputOffset + i] = (byte)(_ivOutput[i] ^ inputBuffer[inputOffset + i]); - } - - var j = IV.Length; - while (--j >= 0 && ++IV[j] == 0) - { - // Intentionally empty block - } - - return _blockSize; + return EncryptBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset); } } } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/OfbCipherMode.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/OfbCipherMode.cs index 1f321f45..e87dc9d3 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/OfbCipherMode.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/Modes/OfbCipherMode.cs @@ -4,7 +4,7 @@ using System.Globalization; namespace Renci.SshNet.Security.Cryptography.Ciphers.Modes { /// - /// Implements OFB cipher mode + /// Implements OFB cipher mode. /// public class OfbCipherMode : CipherMode { @@ -74,32 +74,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers.Modes /// public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { - if (inputBuffer.Length - inputOffset < _blockSize) - { - throw new ArgumentException("Invalid input buffer"); - } - - if (outputBuffer.Length - outputOffset < _blockSize) - { - throw new ArgumentException("Invalid output buffer"); - } - - if (inputCount != _blockSize) - { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "inputCount must be {0}.", _blockSize)); - } - - _ = Cipher.EncryptBlock(IV, 0, IV.Length, _ivOutput, 0); - - for (var i = 0; i < _blockSize; i++) - { - outputBuffer[outputOffset + i] = (byte)(_ivOutput[i] ^ inputBuffer[inputOffset + i]); - } - - Buffer.BlockCopy(IV, _blockSize, IV, 0, IV.Length - _blockSize); - Buffer.BlockCopy(outputBuffer, outputOffset, IV, IV.Length - _blockSize, _blockSize); - - return _blockSize; + return EncryptBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset); } } } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/Paddings/PKCS7Padding.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/Paddings/PKCS7Padding.cs index cfe0ae9a..eb950abf 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/Paddings/PKCS7Padding.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/Paddings/PKCS7Padding.cs @@ -3,7 +3,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers.Paddings { /// - /// Implements PKCS7 cipher padding + /// Implements PKCS7 cipher padding. /// public class PKCS7Padding : CipherPadding { diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs index 749083c0..8cb58a93 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs @@ -36,7 +36,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers // Calculate signature var bitLength = _key.Modulus.BitLength; - var paddedBlock = new byte[bitLength / 8 + (bitLength % 8 > 0 ? 1 : 0) - 1]; + var paddedBlock = new byte[(bitLength / 8) + (bitLength % 8 > 0 ? 1 : 0) - 1]; paddedBlock[0] = 0x01; for (var i = 1; i < paddedBlock.Length - length - 1; i++) @@ -141,7 +141,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers var h = BigInteger.PositiveMod((mP - mQ) * _key.InverseQ, _key.P); - var m = h * _key.Q + mQ; + var m = (h * _key.Q) + mQ; var rInv = BigInteger.ModInverse(random, _key.Modulus); diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/SerpentCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/SerpentCipher.cs index c8032b00..6f19e176 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/SerpentCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/SerpentCipher.cs @@ -871,7 +871,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers } /// - /// InvS3 - { 0, 9,10, 7,11,14, 6,13, 3, 5,12, 2, 4, 8,15, 1 } - 15 terms + /// InvS3 - { 0, 9,10, 7,11,14, 6,13, 3, 5,12, 2, 4, 8,15, 1 } - 15 terms. /// /// A. /// The b. diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.cs index eef40c0e..eb1c0839 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/TripleDesCipher.cs @@ -41,12 +41,12 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers { if ((inputOffset + BlockSize) > inputBuffer.Length) { - throw new IndexOutOfRangeException("input buffer too short"); + throw new ArgumentException("input buffer too short"); } if ((outputOffset + BlockSize) > outputBuffer.Length) { - throw new IndexOutOfRangeException("output buffer too short"); + throw new ArgumentException("output buffer too short"); } if (_encryptionKey1 is null || _encryptionKey2 is null || _encryptionKey3 is null) @@ -97,12 +97,12 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers { if ((inputOffset + BlockSize) > inputBuffer.Length) { - throw new IndexOutOfRangeException("input buffer too short"); + throw new ArgumentException("input buffer too short"); } if ((outputOffset + BlockSize) > outputBuffer.Length) { - throw new IndexOutOfRangeException("output buffer too short"); + throw new ArgumentException("output buffer too short"); } if (_decryptionKey1 is null || _decryptionKey2 is null || _decryptionKey3 is null) diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/TwofishCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/TwofishCipher.cs index 8ecf6793..7ef039cc 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/TwofishCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/TwofishCipher.cs @@ -7,6 +7,72 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers /// public sealed class TwofishCipher : BlockCipher { + /** + * Define the fixed p0/p1 permutations used in keyed S-box lookup. + * By changing the following constant definitions, the S-boxes will + * automatically Get changed in the Twofish engine. + */ +#pragma warning disable SA1310 // Field names should not contain underscore + private const int P_00 = 1; + private const int P_01 = 0; + private const int P_02 = 0; + private const int P_03 = P_01 ^ 1; + private const int P_04 = 1; + + private const int P_10 = 0; + private const int P_11 = 0; + private const int P_12 = 1; + private const int P_13 = P_11 ^ 1; + private const int P_14 = 0; + + private const int P_20 = 1; + private const int P_21 = 1; + private const int P_22 = 0; + private const int P_23 = P_21 ^ 1; + private const int P_24 = 0; + + private const int P_30 = 0; + private const int P_31 = 1; + private const int P_32 = 1; + private const int P_33 = P_31 ^ 1; + private const int P_34 = 1; + + /* Primitive polynomial for GF(256) */ + private const int GF256_FDBK = 0x169; + private const int GF256_FDBK_2 = GF256_FDBK / 2; + private const int GF256_FDBK_4 = GF256_FDBK / 4; + + private const int RS_GF_FDBK = 0x14D; // field generator + + private const int ROUNDS = 16; + private const int MAX_ROUNDS = 16; // bytes = 128 bits + private const int MAX_KEY_BITS = 256; + + private const int INPUT_WHITEN = 0; + private const int OUTPUT_WHITEN = INPUT_WHITEN + (16 / 4); // 4 + private const int ROUND_SUBKEYS = OUTPUT_WHITEN + (16 / 4); // 8 + + private const int TOTAL_SUBKEYS = ROUND_SUBKEYS + (2 * MAX_ROUNDS); // 40 + + private const int SK_STEP = 0x02020202; + private const int SK_BUMP = 0x01010101; + private const int SK_ROTL = 9; +#pragma warning restore SA1310 // Field names should not contain underscore + + private readonly int[] _gMDS0 = new int[MAX_KEY_BITS]; + private readonly int[] _gMDS1 = new int[MAX_KEY_BITS]; + private readonly int[] _gMDS2 = new int[MAX_KEY_BITS]; + private readonly int[] _gMDS3 = new int[MAX_KEY_BITS]; + + private readonly int _k64Cnt; + + /* + * _gSubKeys[] and _gSBox[] are eventually used in the + * encryption and decryption methods. + */ + private int[] _gSubKeys; + private int[] _gSBox; + /// /// Initializes a new instance of the class. /// @@ -80,13 +146,13 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers var t1 = Fe32_3(_gSBox, x1); x2 ^= t0 + t1 + _gSubKeys[k++]; x2 = (int)((uint)x2 >> 1) | x2 << 31; - x3 = (x3 << 1 | (int)((uint)x3 >> 31)) ^ (t0 + 2 * t1 + _gSubKeys[k++]); + x3 = (x3 << 1 | (int)((uint)x3 >> 31)) ^ (t0 + (2 * t1) + _gSubKeys[k++]); t0 = Fe32_0(_gSBox, x2); t1 = Fe32_3(_gSBox, x3); x0 ^= t0 + t1 + _gSubKeys[k++]; x0 = (int)((uint)x0 >> 1) | x0 << 31; - x1 = (x1 << 1 | (int)((uint)x1 >> 31)) ^ (t0 + 2 * t1 + _gSubKeys[k++]); + x1 = (x1 << 1 | (int)((uint)x1 >> 31)) ^ (t0 + (2 * t1) + _gSubKeys[k++]); } Bits32ToBytes(x2 ^ _gSubKeys[OUTPUT_WHITEN], outputBuffer, outputOffset); @@ -115,20 +181,20 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers var x0 = BytesTo32Bits(inputBuffer, inputOffset + 8) ^ _gSubKeys[OUTPUT_WHITEN + 2]; var x1 = BytesTo32Bits(inputBuffer, inputOffset + 12) ^ _gSubKeys[OUTPUT_WHITEN + 3]; - var k = ROUND_SUBKEYS + 2 * ROUNDS - 1; + var k = ROUND_SUBKEYS + (2 * ROUNDS) - 1; for (var r = 0; r < ROUNDS; r += 2) { var t0 = Fe32_0(_gSBox, x2); var t1 = Fe32_3(_gSBox, x3); - x1 ^= t0 + 2 * t1 + _gSubKeys[k--]; - x0 = (x0 << 1 | (int)((uint)x0 >> 31)) ^ (t0 + t1 + _gSubKeys[k--]); - x1 = (int)((uint)x1 >> 1) | x1 << 31; + x1 ^= t0 + (2 * t1) + _gSubKeys[k--]; + x0 = (x0 << 1 | (int) ((uint) x0 >> 31)) ^ (t0 + t1 + _gSubKeys[k--]); + x1 = (int) ((uint) x1 >> 1) | x1 << 31; t0 = Fe32_0(_gSBox, x0); t1 = Fe32_3(_gSBox, x1); - x3 ^= t0 + 2 * t1 + _gSubKeys[k--]; - x2 = (x2 << 1 | (int)((uint)x2 >> 31)) ^ (t0 + t1 + _gSubKeys[k--]); - x3 = (int)((uint)x3 >> 1) | x3 << 31; + x3 ^= t0 + (2 * t1) + _gSubKeys[k--]; + x2 = (x2 << 1 | (int) ((uint) x2 >> 31)) ^ (t0 + t1 + _gSubKeys[k--]); + x3 = (int) ((uint) x3 >> 1) | x3 << 31; } Bits32ToBytes(x0 ^ _gSubKeys[INPUT_WHITEN], outputBuffer, outputOffset); @@ -178,70 +244,6 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers 0xD7, 0x61, 0x1E, 0xB4, 0x50, 0x04, 0xF6, 0xC2, 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xBE, 0x91 }; - /** - * Define the fixed p0/p1 permutations used in keyed S-box lookup. - * By changing the following constant definitions, the S-boxes will - * automatically Get changed in the Twofish engine. - */ - private const int P_00 = 1; - private const int P_01 = 0; - private const int P_02 = 0; - private const int P_03 = P_01 ^ 1; - private const int P_04 = 1; - - private const int P_10 = 0; - private const int P_11 = 0; - private const int P_12 = 1; - private const int P_13 = P_11 ^ 1; - private const int P_14 = 0; - - private const int P_20 = 1; - private const int P_21 = 1; - private const int P_22 = 0; - private const int P_23 = P_21 ^ 1; - private const int P_24 = 0; - - private const int P_30 = 0; - private const int P_31 = 1; - private const int P_32 = 1; - private const int P_33 = P_31 ^ 1; - private const int P_34 = 1; - - /* Primitive polynomial for GF(256) */ - private const int GF256_FDBK = 0x169; - private const int GF256_FDBK_2 = GF256_FDBK / 2; - private const int GF256_FDBK_4 = GF256_FDBK / 4; - - private const int RS_GF_FDBK = 0x14D; // field generator - - private const int ROUNDS = 16; - private const int MAX_ROUNDS = 16; // bytes = 128 bits - private const int MAX_KEY_BITS = 256; - - private const int INPUT_WHITEN = 0; - private const int OUTPUT_WHITEN = INPUT_WHITEN + 16 / 4; // 4 - private const int ROUND_SUBKEYS = OUTPUT_WHITEN + 16 / 4;// 8 - - private const int TOTAL_SUBKEYS = ROUND_SUBKEYS + 2 * MAX_ROUNDS;// 40 - - private const int SK_STEP = 0x02020202; - private const int SK_BUMP = 0x01010101; - private const int SK_ROTL = 9; - - private readonly int[] _gMDS0 = new int[MAX_KEY_BITS]; - private readonly int[] _gMDS1 = new int[MAX_KEY_BITS]; - private readonly int[] _gMDS2 = new int[MAX_KEY_BITS]; - private readonly int[] _gMDS3 = new int[MAX_KEY_BITS]; - - private readonly int _k64Cnt; - - /** - * _gSubKeys[] and _gSBox[] are eventually used in the - * encryption and decryption methods. - */ - private int[] _gSubKeys; - private int[] _gSBox; - private void SetKey(byte[] key) { var k32e = new int[MAX_KEY_BITS / 64]; // 4 @@ -285,7 +287,7 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers a += b; _gSubKeys[i * 2] = a; a += b; - _gSubKeys[i * 2 + 1] = a << SK_ROTL | (int)((uint)a >> (32 - SK_ROTL)); + _gSubKeys[(i * 2) + 1] = a << SK_ROTL | (int)((uint)a >> (32 - SK_ROTL)); } /* @@ -305,28 +307,28 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers switch (_k64Cnt & 3) { case 1: - _gSBox[i * 2] = _gMDS0[(P[P_01 * 256 + b0] & 0xff) ^ M_b0(k0)]; - _gSBox[i * 2 + 1] = _gMDS1[(P[P_11 * 256 + b1] & 0xff) ^ M_b1(k0)]; - _gSBox[i * 2 + 0x200] = _gMDS2[(P[P_21 * 256 + b2] & 0xff) ^ M_b2(k0)]; - _gSBox[i * 2 + 0x201] = _gMDS3[(P[P_31 * 256 + b3] & 0xff) ^ M_b3(k0)]; + _gSBox[i * 2] = _gMDS0[(P[(P_01 * 256) + b0] & 0xff) ^ M_b0(k0)]; + _gSBox[(i * 2) + 1] = _gMDS1[(P[(P_11 * 256) + b1] & 0xff) ^ M_b1(k0)]; + _gSBox[(i * 2) + 0x200] = _gMDS2[(P[(P_21 * 256) + b2] & 0xff) ^ M_b2(k0)]; + _gSBox[(i * 2) + 0x201] = _gMDS3[(P[(P_31 * 256) + b3] & 0xff) ^ M_b3(k0)]; break; case 0: /* 256 bits of key */ - b0 = (P[P_04 * 256 + b0] & 0xff) ^ M_b0(k3); - b1 = (P[P_14 * 256 + b1] & 0xff) ^ M_b1(k3); - b2 = (P[P_24 * 256 + b2] & 0xff) ^ M_b2(k3); - b3 = (P[P_34 * 256 + b3] & 0xff) ^ M_b3(k3); + b0 = (P[(P_04 * 256) + b0] & 0xff) ^ M_b0(k3); + b1 = (P[(P_14 * 256) + b1] & 0xff) ^ M_b1(k3); + b2 = (P[(P_24 * 256) + b2] & 0xff) ^ M_b2(k3); + b3 = (P[(P_34 * 256) + b3] & 0xff) ^ M_b3(k3); goto case 3; case 3: - b0 = (P[P_03 * 256 + b0] & 0xff) ^ M_b0(k2); - b1 = (P[P_13 * 256 + b1] & 0xff) ^ M_b1(k2); - b2 = (P[P_23 * 256 + b2] & 0xff) ^ M_b2(k2); - b3 = (P[P_33 * 256 + b3] & 0xff) ^ M_b3(k2); + b0 = (P[(P_03 * 256) + b0] & 0xff) ^ M_b0(k2); + b1 = (P[(P_13 * 256) + b1] & 0xff) ^ M_b1(k2); + b2 = (P[(P_23 * 256) + b2] & 0xff) ^ M_b2(k2); + b3 = (P[(P_33 * 256) + b3] & 0xff) ^ M_b3(k2); goto case 2; case 2: - _gSBox[i * 2] = _gMDS0[(P[P_01 * 256 + (P[P_02 * 256 + b0] & 0xff) ^ M_b0(k1)] & 0xff) ^ M_b0(k0)]; - _gSBox[i * 2 + 1] = _gMDS1[(P[P_11 * 256 + (P[P_12 * 256 + b1] & 0xff) ^ M_b1(k1)] & 0xff) ^ M_b1(k0)]; - _gSBox[i * 2 + 0x200] = _gMDS2[(P[P_21 * 256 + (P[P_22 * 256 + b2] & 0xff) ^ M_b2(k1)] & 0xff) ^ M_b2(k0)]; - _gSBox[i * 2 + 0x201] = _gMDS3[(P[P_31 * 256 + (P[P_32 * 256 + b3] & 0xff) ^ M_b3(k1)] & 0xff) ^ M_b3(k0)]; + _gSBox[i * 2] = _gMDS0[(P[(P_01 * 256) + (P[(P_02 * 256) + b0] & 0xff) ^ M_b0(k1)] & 0xff) ^ M_b0(k0)]; + _gSBox[(i * 2) + 1] = _gMDS1[(P[(P_11 * 256) + (P[(P_12 * 256) + b1] & 0xff) ^ M_b1(k1)] & 0xff) ^ M_b1(k0)]; + _gSBox[(i * 2) + 0x200] = _gMDS2[(P[(P_21 * 256) + (P[(P_22 * 256) + b2] & 0xff) ^ M_b2(k1)] & 0xff) ^ M_b2(k0)]; + _gSBox[(i * 2) + 0x201] = _gMDS3[(P[(P_31 * 256) + (P[(P_32 * 256) + b3] & 0xff) ^ M_b3(k1)] & 0xff) ^ M_b3(k0)]; break; } #pragma warning restore IDE0010 // Add missing cases @@ -360,29 +362,29 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers switch (_k64Cnt & 3) { case 1: - result = _gMDS0[(P[P_01 * 256 + b0] & 0xff) ^ M_b0(k0)] ^ - _gMDS1[(P[P_11 * 256 + b1] & 0xff) ^ M_b1(k0)] ^ - _gMDS2[(P[P_21 * 256 + b2] & 0xff) ^ M_b2(k0)] ^ - _gMDS3[(P[P_31 * 256 + b3] & 0xff) ^ M_b3(k0)]; + result = _gMDS0[(P[(P_01 * 256) + b0] & 0xff) ^ M_b0(k0)] ^ + _gMDS1[(P[(P_11 * 256) + b1] & 0xff) ^ M_b1(k0)] ^ + _gMDS2[(P[(P_21 * 256) + b2] & 0xff) ^ M_b2(k0)] ^ + _gMDS3[(P[(P_31 * 256) + b3] & 0xff) ^ M_b3(k0)]; break; case 0: /* 256 bits of key */ - b0 = (P[P_04 * 256 + b0] & 0xff) ^ M_b0(k3); - b1 = (P[P_14 * 256 + b1] & 0xff) ^ M_b1(k3); - b2 = (P[P_24 * 256 + b2] & 0xff) ^ M_b2(k3); - b3 = (P[P_34 * 256 + b3] & 0xff) ^ M_b3(k3); + b0 = (P[(P_04 * 256) + b0] & 0xff) ^ M_b0(k3); + b1 = (P[(P_14 * 256) + b1] & 0xff) ^ M_b1(k3); + b2 = (P[(P_24 * 256) + b2] & 0xff) ^ M_b2(k3); + b3 = (P[(P_34 * 256) + b3] & 0xff) ^ M_b3(k3); goto case 3; case 3: - b0 = (P[P_03 * 256 + b0] & 0xff) ^ M_b0(k2); - b1 = (P[P_13 * 256 + b1] & 0xff) ^ M_b1(k2); - b2 = (P[P_23 * 256 + b2] & 0xff) ^ M_b2(k2); - b3 = (P[P_33 * 256 + b3] & 0xff) ^ M_b3(k2); + b0 = (P[(P_03 * 256) + b0] & 0xff) ^ M_b0(k2); + b1 = (P[(P_13 * 256) + b1] & 0xff) ^ M_b1(k2); + b2 = (P[(P_23 * 256) + b2] & 0xff) ^ M_b2(k2); + b3 = (P[(P_33 * 256) + b3] & 0xff) ^ M_b3(k2); goto case 2; case 2: result = - _gMDS0[(P[P_01 * 256 + (P[P_02 * 256 + b0] & 0xff) ^ M_b0(k1)] & 0xff) ^ M_b0(k0)] ^ - _gMDS1[(P[P_11 * 256 + (P[P_12 * 256 + b1] & 0xff) ^ M_b1(k1)] & 0xff) ^ M_b1(k0)] ^ - _gMDS2[(P[P_21 * 256 + (P[P_22 * 256 + b2] & 0xff) ^ M_b2(k1)] & 0xff) ^ M_b2(k0)] ^ - _gMDS3[(P[P_31 * 256 + (P[P_32 * 256 + b3] & 0xff) ^ M_b3(k1)] & 0xff) ^ M_b3(k0)]; + _gMDS0[(P[(P_01 * 256) + (P[(P_02 * 256) + b0] & 0xff) ^ M_b0(k1)] & 0xff) ^ M_b0(k0)] ^ + _gMDS1[(P[(P_11 * 256) + (P[(P_12 * 256) + b1] & 0xff) ^ M_b1(k1)] & 0xff) ^ M_b1(k0)] ^ + _gMDS2[(P[(P_21 * 256) + (P[(P_22 * 256) + b2] & 0xff) ^ M_b2(k1)] & 0xff) ^ M_b2(k0)] ^ + _gMDS3[(P[(P_31 * 256) + (P[(P_32 * 256) + b3] & 0xff) ^ M_b3(k1)] & 0xff) ^ M_b3(k0)]; break; } #pragma warning restore IDE0010 // Add missing cases @@ -423,14 +425,14 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers *
         * G(x) = x^4 + (a+1/a)x^3 + ax^2 + (a+1/a)x + 1
         * 
- * where a = primitive root of field generator 0x14D + * where a = primitive root of field generator 0x14D. *

*/ private static int RS_rem(int x) { - var b = (int)(((uint)x >> 24) & 0xff); + var b = (int) (((uint) x >> 24) & 0xff); var g2 = ((b << 1) ^ ((b & 0x80) != 0 ? RS_GF_FDBK : 0)) & 0xff; - var g3 = ((int)((uint)b >> 1) ^ ((b & 0x01) != 0 ? (int)((uint)RS_GF_FDBK >> 1) : 0)) ^ g2; + var g3 = ((int) ((uint) b >> 1) ^ ((b & 0x01) != 0 ? (int) ((uint) RS_GF_FDBK >> 1) : 0)) ^ g2; return (x << 8) ^ (g3 << 24) ^ (g2 << 16) ^ (g3 << 8) ^ b; } @@ -484,18 +486,18 @@ namespace Renci.SshNet.Security.Cryptography.Ciphers private static int Fe32_0(int[] gSBox1, int x) { - return gSBox1[0x000 + 2 * (x & 0xff)] ^ - gSBox1[0x001 + 2 * ((int)((uint)x >> 8) & 0xff)] ^ - gSBox1[0x200 + 2 * ((int)((uint)x >> 16) & 0xff)] ^ - gSBox1[0x201 + 2 * ((int)((uint)x >> 24) & 0xff)]; + return gSBox1[0x000 + (2 * (x & 0xff))] ^ + gSBox1[0x001 + (2 * ((int)((uint)x >> 8) & 0xff))] ^ + gSBox1[0x200 + (2 * ((int)((uint)x >> 16) & 0xff))] ^ + gSBox1[0x201 + (2 * ((int)((uint)x >> 24) & 0xff))]; } private static int Fe32_3(int[] gSBox1, int x) { - return gSBox1[0x000 + 2 * ((int)((uint)x >> 24) & 0xff)] ^ - gSBox1[0x001 + 2 * (x & 0xff)] ^ - gSBox1[0x200 + 2 * ((int)((uint)x >> 8) & 0xff)] ^ - gSBox1[0x201 + 2 * ((int)((uint)x >> 16) & 0xff)]; + return gSBox1[0x000 + (2 * ((int) ((uint) x >> 24) & 0xff))] ^ + gSBox1[0x001 + (2 * (x & 0xff))] ^ + gSBox1[0x200 + (2 * ((int)((uint)x >> 8) & 0xff))] ^ + gSBox1[0x201 + (2 * ((int)((uint)x >> 16) & 0xff))]; } private static int BytesTo32Bits(byte[] b, int p) diff --git a/src/Renci.SshNet/Security/Cryptography/DigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/DigitalSignature.cs index e675fe32..3e7cee7d 100644 --- a/src/Renci.SshNet/Security/Cryptography/DigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/DigitalSignature.cs @@ -1,7 +1,7 @@ namespace Renci.SshNet.Security.Cryptography { /// - /// Base class for signature implementations + /// Base class for signature implementations. /// public abstract class DigitalSignature { @@ -10,7 +10,9 @@ /// /// The input. /// The signature. - /// if signature was successfully verified; otherwise . + /// + /// if signature was successfully verified; otherwise . + /// public abstract bool Verify(byte[] input, byte[] signature); /// diff --git a/src/Renci.SshNet/Security/Cryptography/DsaDigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/DsaDigitalSignature.cs index 03b4d016..a5aae17c 100644 --- a/src/Renci.SshNet/Security/Cryptography/DsaDigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/DsaDigitalSignature.cs @@ -80,10 +80,10 @@ namespace Renci.SshNet.Security.Cryptography var w = BigInteger.ModInverse(s, _key.Q); // Calculate u1 = H(m)·w mod q - var u1 = hm * w % _key.Q; + var u1 = (hm * w) % _key.Q; // Calculate u2 = r * w mod q - var u2 = r * w % _key.Q; + var u2 = (r * w) % _key.Q; u1 = BigInteger.ModPow(_key.G, u1, _key.P); u2 = BigInteger.ModPow(_key.Y, u2, _key.P); diff --git a/src/Renci.SshNet/Security/Cryptography/DsaKey.cs b/src/Renci.SshNet/Security/Cryptography/DsaKey.cs index cdd0c4de..65c6fcee 100644 --- a/src/Renci.SshNet/Security/Cryptography/DsaKey.cs +++ b/src/Renci.SshNet/Security/Cryptography/DsaKey.cs @@ -160,7 +160,7 @@ namespace Renci.SshNet.Security } /// - /// Releases unmanaged and - optionally - managed resources + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) diff --git a/src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs index 1e8e21bc..2d3cdb95 100644 --- a/src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/ED25519DigitalSignature.cs @@ -64,7 +64,7 @@ namespace Renci.SshNet.Security.Cryptography } /// - /// Releases unmanaged and - optionally - managed resources + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) diff --git a/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs b/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs index 48c3a74b..bb003794 100644 --- a/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs +++ b/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs @@ -11,17 +11,21 @@ namespace Renci.SshNet.Security /// public class ED25519Key : Key, IDisposable { - private ED25519DigitalSignature _digitalSignature; - - private byte[] _publicKey = new byte[Ed25519.PublicKeySizeInBytes]; #pragma warning disable IDE1006 // Naming Styles +#pragma warning disable SX1309 // Field names should begin with underscore private readonly byte[] privateKey = new byte[Ed25519.ExpandedPrivateKeySizeInBytes]; +#pragma warning restore SX1309 // Field names should begin with underscore #pragma warning restore IDE1006 // Naming Styles + private ED25519DigitalSignature _digitalSignature; + private byte[] _publicKey = new byte[Ed25519.PublicKeySizeInBytes]; private bool _isDisposed; /// - /// Gets the Key String. + /// Gets the name of the key. /// + /// + /// The name of the key. + /// public override string ToString() { return "ssh-ed25519"; @@ -132,7 +136,7 @@ namespace Renci.SshNet.Security } /// - /// Releases unmanaged and - optionally - managed resources + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) diff --git a/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs index 5c9025d5..74f11434 100644 --- a/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs @@ -83,7 +83,7 @@ namespace Renci.SshNet.Security.Cryptography } /// - /// Releases unmanaged and - optionally - managed resources + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) @@ -106,81 +106,81 @@ namespace Renci.SshNet.Security.Cryptography { Dispose(disposing: false); } - } - internal sealed class SshDataSignature : SshData - { - private readonly int _signature_size; - - private byte[] _signature_r; - private byte[] _signature_s; - - public byte[] Signature + private sealed class SshDataSignature : SshData { - get + private readonly int _signature_size; + + private byte[] _signature_r; + private byte[] _signature_s; + + public byte[] Signature { - var signature = new byte[_signature_size]; - Buffer.BlockCopy(_signature_r, 0, signature, 0, _signature_r.Length); - Buffer.BlockCopy(_signature_s, 0, signature, _signature_r.Length, _signature_s.Length); - return signature; - } - set - { - var signed_r = new byte[_signature_size / 2]; - Buffer.BlockCopy(value, 0, signed_r, 0, signed_r.Length); - _signature_r = signed_r.ToBigInteger2().ToByteArray().Reverse(); + get + { + var signature = new byte[_signature_size]; + Buffer.BlockCopy(_signature_r, 0, signature, 0, _signature_r.Length); + Buffer.BlockCopy(_signature_s, 0, signature, _signature_r.Length, _signature_s.Length); + return signature; + } + set + { + var signed_r = new byte[_signature_size / 2]; + Buffer.BlockCopy(value, 0, signed_r, 0, signed_r.Length); + _signature_r = signed_r.ToBigInteger2().ToByteArray().Reverse(); - var signed_s = new byte[_signature_size / 2]; - Buffer.BlockCopy(value, signed_r.Length, signed_s, 0, signed_s.Length); - _signature_s = signed_s.ToBigInteger2().ToByteArray().Reverse(); - } - } - - public SshDataSignature(int sig_size) - { - _signature_size = sig_size; - } - - public SshDataSignature(byte[] data, int sig_size) - { - _signature_size = sig_size; - Load(data); - } - - protected override void LoadData() - { - _signature_r = ReadBinary().TrimLeadingZeros().Pad(_signature_size / 2); - _signature_s = ReadBinary().TrimLeadingZeros().Pad(_signature_size / 2); - } - - protected override void SaveData() - { - WriteBinaryString(_signature_r.ToBigInteger2().ToByteArray().Reverse()); - WriteBinaryString(_signature_s.ToBigInteger2().ToByteArray().Reverse()); - } - - public new byte[] ReadBinary() - { - var length = ReadUInt32(); - - if (length > int.MaxValue) - { - throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue)); + var signed_s = new byte[_signature_size / 2]; + Buffer.BlockCopy(value, signed_r.Length, signed_s, 0, signed_s.Length); + _signature_s = signed_s.ToBigInteger2().ToByteArray().Reverse(); + } } - return ReadBytes((int)length); - } - - protected override int BufferCapacity - { - get + public SshDataSignature(int sig_size) { - var capacity = base.BufferCapacity; - capacity += 4; // r length - capacity += _signature_r.Length; // signature r - capacity += 4; // s length - capacity += _signature_s.Length; // signature s - return capacity; + _signature_size = sig_size; + } + + public SshDataSignature(byte[] data, int sig_size) + { + _signature_size = sig_size; + Load(data); + } + + protected override void LoadData() + { + _signature_r = ReadBinary().TrimLeadingZeros().Pad(_signature_size / 2); + _signature_s = ReadBinary().TrimLeadingZeros().Pad(_signature_size / 2); + } + + protected override void SaveData() + { + WriteBinaryString(_signature_r.ToBigInteger2().ToByteArray().Reverse()); + WriteBinaryString(_signature_s.ToBigInteger2().ToByteArray().Reverse()); + } + + public new byte[] ReadBinary() + { + var length = ReadUInt32(); + + if (length > int.MaxValue) + { + throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue)); + } + + return ReadBytes((int) length); + } + + protected override int BufferCapacity + { + get + { + var capacity = base.BufferCapacity; + capacity += 4; // r length + capacity += _signature_r.Length; // signature r + capacity += 4; // s length + capacity += _signature_s.Length; // signature s + return capacity; + } } } } diff --git a/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs b/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs index fe38dfec..455bb136 100644 --- a/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs +++ b/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs @@ -1,12 +1,11 @@ using System; #if NETFRAMEWORK +using System.Globalization; using System.IO; -#endif // NETFRAMEWORK -using System.Text; -#if NETFRAMEWORK using System.Runtime.InteropServices; #endif // NETFRAMEWORK using System.Security.Cryptography; +using System.Text; using Renci.SshNet.Common; using Renci.SshNet.Security.Cryptography; @@ -18,9 +17,11 @@ namespace Renci.SshNet.Security /// public class EcdsaKey : Key, IDisposable { - internal const string ECDSA_P256_OID_VALUE = "1.2.840.10045.3.1.7"; // Also called nistP256 or secP256r1 - internal const string ECDSA_P384_OID_VALUE = "1.3.132.0.34"; // Also called nistP384 or secP384r1 - internal const string ECDSA_P521_OID_VALUE = "1.3.132.0.35"; // Also called nistP521or secP521r1 +#pragma warning disable SA1310 // Field names should not contain underscore + private const string ECDSA_P256_OID_VALUE = "1.2.840.10045.3.1.7"; // Also called nistP256 or secP256r1 + private const string ECDSA_P384_OID_VALUE = "1.3.132.0.34"; // Also called nistP384 or secP384r1 + private const string ECDSA_P521_OID_VALUE = "1.3.132.0.35"; // Also called nistP521or secP521r1 +#pragma warning restore SA1310 // Field names should not contain underscore private EcdsaDigitalSignature _digitalSignature; private bool _isDisposed; @@ -52,7 +53,7 @@ namespace Renci.SshNet.Security internal struct BCRYPT_ECCKEY_BLOB { internal KeyBlobMagicNumber Magic; - internal int cbKey; + internal int CbKey; } #endif @@ -69,7 +70,7 @@ namespace Renci.SshNet.Security #if NETFRAMEWORK /// - /// Gets the HashAlgorithm to use + /// Gets the HashAlgorithm to use. /// public CngAlgorithm HashAlgorithm { @@ -84,7 +85,7 @@ namespace Renci.SshNet.Security case 521: return CngAlgorithm.Sha512; default: - throw new SshException("Unknown KeySize: " + Ecdsa.KeySize); + throw new SshException("Unknown KeySize: " + Ecdsa.KeySize.ToString(CultureInfo.InvariantCulture)); } } } @@ -380,6 +381,7 @@ namespace Renci.SshNet.Security bw.Write(privatekey); // d } } + _key = CngKey.Import(blob, privatekey is null ? CngKeyBlobFormat.EccPublicBlob : CngKeyBlobFormat.EccPrivateBlob); Ecdsa = new ECDsaCng(_key); @@ -413,13 +415,13 @@ namespace Renci.SshNet.Security private static string GetCurveOid(string curve_s) { - switch (curve_s.ToLower()) + switch (curve_s.ToUpperInvariant()) { - case "nistp256": + case "NISTP256": return ECDSA_P256_OID_VALUE; - case "nistp384": + case "NISTP384": return ECDSA_P384_OID_VALUE; - case "nistp521": + case "NISTP521": return ECDSA_P521_OID_VALUE; default: throw new SshException("Unexpected Curve Name: " + curve_s); diff --git a/src/Renci.SshNet/Security/Cryptography/HMACMD5.cs b/src/Renci.SshNet/Security/Cryptography/HMACMD5.cs index cd129fe6..bd958544 100644 --- a/src/Renci.SshNet/Security/Cryptography/HMACMD5.cs +++ b/src/Renci.SshNet/Security/Cryptography/HMACMD5.cs @@ -11,22 +11,29 @@ namespace Renci.SshNet.Security.Cryptography private readonly int _hashSize; /// - /// Initializes a with the specified key. + /// Initializes a new instance of the class with the specified key. /// /// The key. public HMACMD5(byte[] key) +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms : base(key) +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms { +#pragma warning disable MA0056 // Do not call overridable members in constructor _hashSize = base.HashSize; +#pragma warning restore MA0056 // Do not call overridable members in constructor } /// - /// Initializes a with the specified key and size of the computed hash code. + /// Initializes a new instance of the class with the specified key + /// and size of the computed hash code. /// /// The key. /// The size, in bits, of the computed hash code. public HMACMD5(byte[] key, int hashSize) +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms : base(key) +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms { _hashSize = hashSize; } @@ -50,7 +57,9 @@ namespace Renci.SshNet.Security.Cryptography /// protected override byte[] HashFinal() { +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms var hash = base.HashFinal(); +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms return hash.Take(HashSize / 8); } } diff --git a/src/Renci.SshNet/Security/Cryptography/HMACSHA1.cs b/src/Renci.SshNet/Security/Cryptography/HMACSHA1.cs index 05a9730e..ca4adfda 100644 --- a/src/Renci.SshNet/Security/Cryptography/HMACSHA1.cs +++ b/src/Renci.SshNet/Security/Cryptography/HMACSHA1.cs @@ -16,9 +16,13 @@ namespace Renci.SshNet.Security.Cryptography /// /// The key. public HMACSHA1(byte[] key) +#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms : base(key) +#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms { +#pragma warning disable MA0056 // Do not call overridable members in constructor _hashSize = base.HashSize; +#pragma warning restore MA0056 // Do not call overridable members in constructor } /// @@ -27,7 +31,9 @@ namespace Renci.SshNet.Security.Cryptography /// The key. /// The size, in bits, of the computed hash code. public HMACSHA1(byte[] key, int hashSize) +#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms : base(key) +#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms { _hashSize = hashSize; } @@ -51,7 +57,9 @@ namespace Renci.SshNet.Security.Cryptography /// protected override byte[] HashFinal() { +#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms var hash = base.HashFinal(); +#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms return hash.Take(HashSize / 8); } } diff --git a/src/Renci.SshNet/Security/Cryptography/HMACSHA256.cs b/src/Renci.SshNet/Security/Cryptography/HMACSHA256.cs index 2598704e..20f752a8 100644 --- a/src/Renci.SshNet/Security/Cryptography/HMACSHA256.cs +++ b/src/Renci.SshNet/Security/Cryptography/HMACSHA256.cs @@ -11,17 +11,20 @@ namespace Renci.SshNet.Security.Cryptography private readonly int _hashSize; /// - /// Initializes a with the specified key. + /// Initializes a new instance of the class with the specified key. /// /// The key. public HMACSHA256(byte[] key) : base(key) { +#pragma warning disable MA0056 // Do not call overridable members in constructor _hashSize = base.HashSize; +#pragma warning restore MA0056 // Do not call overridable members in constructor } /// - /// Initializes a with the specified key and size of the computed hash code. + /// Initializes a new instance of the class with the specified key + /// and size of the computed hash code. /// /// The key. /// The size, in bits, of the computed hash code. @@ -48,7 +51,6 @@ namespace Renci.SshNet.Security.Cryptography /// /// The computed hash code. /// - protected override byte[] HashFinal() { var hash = base.HashFinal(); diff --git a/src/Renci.SshNet/Security/Cryptography/HMACSHA384.cs b/src/Renci.SshNet/Security/Cryptography/HMACSHA384.cs index f5f0b26c..e13d720c 100644 --- a/src/Renci.SshNet/Security/Cryptography/HMACSHA384.cs +++ b/src/Renci.SshNet/Security/Cryptography/HMACSHA384.cs @@ -11,17 +11,20 @@ namespace Renci.SshNet.Security.Cryptography private readonly int _hashSize; /// - /// Initializes a with the specified key. + /// Initializes a new instance of the class with the specified key. /// /// The key. public HMACSHA384(byte[] key) : base(key) { +#pragma warning disable MA0056 // Do not call overridable members in constructor _hashSize = base.HashSize; +#pragma warning restore MA0056 // Do not call overridable members in constructor } /// - /// Initializes a with the specified key and size of the computed hash code. + /// Initializes a new instance of the class with the specified key + /// and size of the computed hash code. /// /// The key. /// The size, in bits, of the computed hash code. diff --git a/src/Renci.SshNet/Security/Cryptography/HMACSHA512.cs b/src/Renci.SshNet/Security/Cryptography/HMACSHA512.cs index 72e75815..8d756efe 100644 --- a/src/Renci.SshNet/Security/Cryptography/HMACSHA512.cs +++ b/src/Renci.SshNet/Security/Cryptography/HMACSHA512.cs @@ -11,17 +11,20 @@ namespace Renci.SshNet.Security.Cryptography private readonly int _hashSize; /// - /// Initializes a with the specified key. + /// Initializes a new instance of the class with the specified key. /// /// The key. public HMACSHA512(byte[] key) : base(key) { +#pragma warning disable MA0056 // Do not call overridable members in constructor _hashSize = base.HashSize; +#pragma warning restore MA0056 // Do not call overridable members in constructor } /// - /// Initializes a with the specified key and size of the computed hash code. + /// Initializes a new instance of the class with the specified key + /// and size of the computed hash code. /// /// The key. /// The size, in bits, of the computed hash code. diff --git a/src/Renci.SshNet/Security/Cryptography/Key.cs b/src/Renci.SshNet/Security/Cryptography/Key.cs index 3110a2b9..81580a1a 100644 --- a/src/Renci.SshNet/Security/Cryptography/Key.cs +++ b/src/Renci.SshNet/Security/Cryptography/Key.cs @@ -14,7 +14,9 @@ namespace Renci.SshNet.Security /// /// Specifies array of big integers that represent private key. /// +#pragma warning disable SA1401 // Fields should be private protected BigInteger[] _privateKey; +#pragma warning restore SA1401 // Fields should be private /// /// Gets the default digital signature implementation for this key. diff --git a/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs index ccb252ef..00a4898f 100644 --- a/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs @@ -18,7 +18,8 @@ namespace Renci.SshNet.Security.Cryptography /// The RSA key. public RsaDigitalSignature(RsaKey rsaKey) : this(rsaKey, HashAlgorithmName.SHA1) - { } + { + } /// /// Initializes a new instance of the class. @@ -58,7 +59,7 @@ namespace Renci.SshNet.Security.Cryptography } /// - /// Releases unmanaged and - optionally - managed resources + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) diff --git a/src/Renci.SshNet/Security/Cryptography/RsaKey.cs b/src/Renci.SshNet/Security/Cryptography/RsaKey.cs index 2f1367a2..19c8bedc 100644 --- a/src/Renci.SshNet/Security/Cryptography/RsaKey.cs +++ b/src/Renci.SshNet/Security/Cryptography/RsaKey.cs @@ -5,15 +5,19 @@ using Renci.SshNet.Security.Cryptography; namespace Renci.SshNet.Security { /// - /// Contains RSA private and public key + /// Contains the RSA private and public key. /// public class RsaKey : Key, IDisposable { private bool _isDisposed; + private RsaDigitalSignature _digitalSignature; /// - /// Gets the Key String. + /// Gets the name of the key. /// + /// + /// The name of the key. + /// public override string ToString() { return "ssh-rsa"; @@ -22,6 +26,9 @@ namespace Renci.SshNet.Security /// /// Gets the modulus. /// + /// + /// The modulus. + /// public BigInteger Modulus { get @@ -33,6 +40,9 @@ namespace Renci.SshNet.Security /// /// Gets the exponent. /// + /// + /// The exponent. + /// public BigInteger Exponent { get @@ -44,6 +54,9 @@ namespace Renci.SshNet.Security /// /// Gets the D. /// + /// + /// The D. + /// public BigInteger D { get @@ -60,6 +73,9 @@ namespace Renci.SshNet.Security /// /// Gets the P. /// + /// + /// The P. + /// public BigInteger P { get @@ -69,7 +85,6 @@ namespace Renci.SshNet.Security return _privateKey[3]; } - return BigInteger.Zero; } } @@ -77,6 +92,9 @@ namespace Renci.SshNet.Security /// /// Gets the Q. /// + /// + /// The Q. + /// public BigInteger Q { get @@ -93,6 +111,9 @@ namespace Renci.SshNet.Security /// /// Gets the DP. /// + /// + /// The DP. + /// public BigInteger DP { get @@ -109,6 +130,9 @@ namespace Renci.SshNet.Security /// /// Gets the DQ. /// + /// + /// The DQ. + /// public BigInteger DQ { get @@ -125,6 +149,9 @@ namespace Renci.SshNet.Security /// /// Gets the inverse Q. /// + /// + /// The inverse Q. + /// public BigInteger InverseQ { get @@ -152,10 +179,8 @@ namespace Renci.SshNet.Security } } - private RsaDigitalSignature _digitalSignature; - /// - /// + /// Gets the digital signature implementation for this key. /// /// /// An implementation of an RSA digital signature using the SHA-1 hash algorithm. @@ -253,7 +278,7 @@ namespace Renci.SshNet.Security } /// - /// Releases unmanaged and - optionally - managed resources + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) diff --git a/src/Renci.SshNet/Security/GroupExchangeHashData.cs b/src/Renci.SshNet/Security/GroupExchangeHashData.cs index 82b889bb..92184109 100644 --- a/src/Renci.SshNet/Security/GroupExchangeHashData.cs +++ b/src/Renci.SshNet/Security/GroupExchangeHashData.cs @@ -16,7 +16,6 @@ namespace Renci.SshNet.Security set { _serverVersion = Utf8.GetBytes(value); } } - public string ClientVersion { private get { return Utf8.GetString(_clientVersion, 0, _clientVersion.Length); } diff --git a/src/Renci.SshNet/Security/KeyExchange.cs b/src/Renci.SshNet/Security/KeyExchange.cs index 6d8c5374..5024941b 100644 --- a/src/Renci.SshNet/Security/KeyExchange.cs +++ b/src/Renci.SshNet/Security/KeyExchange.cs @@ -13,7 +13,7 @@ using Renci.SshNet.Security.Cryptography; namespace Renci.SshNet.Security { /// - /// Represents base class for different key exchange algorithm implementations + /// Represents base class for different key exchange algorithm implementations. /// public abstract class KeyExchange : Algorithm, IKeyExchange { @@ -511,7 +511,7 @@ namespace Renci.SshNet.Security } /// - /// Releases unmanaged and - optionally - managed resources + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) diff --git a/src/Renci.SshNet/Security/KeyExchangeDiffieHellman.cs b/src/Renci.SshNet/Security/KeyExchangeDiffieHellman.cs index 5cdba5b4..4f31514a 100644 --- a/src/Renci.SshNet/Security/KeyExchangeDiffieHellman.cs +++ b/src/Renci.SshNet/Security/KeyExchangeDiffieHellman.cs @@ -6,10 +6,11 @@ using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Security { /// - /// Represents base class for Diffie Hellman key exchange algorithm + /// Represents base class for Diffie Hellman key exchange algorithm. /// internal abstract class KeyExchangeDiffieHellman : KeyExchange { +#pragma warning disable SA1401 // Fields should be private /// /// Specifies key exchange group number. /// @@ -26,7 +27,7 @@ namespace Renci.SshNet.Security protected byte[] _clientPayload; /// - /// Specifies server payload + /// Specifies server payload. /// protected byte[] _serverPayload; @@ -54,6 +55,7 @@ namespace Renci.SshNet.Security /// Specifies signature data. /// protected byte[] _signature; +#pragma warning restore SA1401 // Fields should be private /// /// Gets the size, in bits, of the computed hash code. diff --git a/src/Renci.SshNet/Security/KeyExchangeEC.cs b/src/Renci.SshNet/Security/KeyExchangeEC.cs index f2ae22fb..4368affb 100644 --- a/src/Renci.SshNet/Security/KeyExchangeEC.cs +++ b/src/Renci.SshNet/Security/KeyExchangeEC.cs @@ -4,13 +4,14 @@ namespace Renci.SshNet.Security { internal abstract class KeyExchangeEC : KeyExchange { +#pragma warning disable SA1401 // Fields should be private /// - /// Specifies client payload + /// Specifies client payload. /// protected byte[] _clientPayload; /// - /// Specifies server payload + /// Specifies server payload. /// protected byte[] _serverPayload; @@ -33,6 +34,7 @@ namespace Renci.SshNet.Security /// Specifies signature data. /// protected byte[] _signature; +#pragma warning restore SA1401 // Fields should be private /// /// Gets the size, in bits, of the computed hash code. diff --git a/src/Renci.SshNet/Security/KeyExchangeHash.cs b/src/Renci.SshNet/Security/KeyExchangeHashData.cs similarity index 100% rename from src/Renci.SshNet/Security/KeyExchangeHash.cs rename to src/Renci.SshNet/Security/KeyExchangeHashData.cs diff --git a/src/Renci.SshNet/Security/KeyHostAlgorithm.cs b/src/Renci.SshNet/Security/KeyHostAlgorithm.cs index f0d2b41d..0a0232a5 100644 --- a/src/Renci.SshNet/Security/KeyHostAlgorithm.cs +++ b/src/Renci.SshNet/Security/KeyHostAlgorithm.cs @@ -13,18 +13,27 @@ namespace Renci.SshNet.Security public class KeyHostAlgorithm : HostAlgorithm { /// - /// The key used in this host key algorithm. + /// Gets the key used in this host key algorithm. /// + /// + /// The key used in this host key algorithm. + /// public Key Key { get; private set; } /// - /// The signature implementation used in this host key algorithm. + /// Gets the signature implementation used in this host key algorithm. /// + /// + /// The signature implementation used in this host key algorithm. + /// public DigitalSignature DigitalSignature { get; private set; } /// /// Gets the encoded public key data. /// + /// + /// The encoded public key data. + /// public override byte[] Data { get @@ -202,11 +211,13 @@ namespace Renci.SshNet.Security var capacity = base.BufferCapacity; capacity += 4; // Name length capacity += _name.Length; // Name + foreach (var key in _keys) { capacity += 4; // Key length capacity += key.Length; // Key } + return capacity; } } @@ -246,8 +257,11 @@ namespace Renci.SshNet.Security internal sealed class SignatureKeyData : SshData { /// - /// Gets or sets the signature format identifier + /// Gets or sets the signature format identifier. /// + /// + /// The signature format identifier. + /// public string AlgorithmName { get; set; } /// diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 23eef6f7..2d913d07 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -121,6 +121,12 @@ namespace Renci.SshNet /// private readonly object _socketDisposeLock = new object(); + /// + /// Holds an object that is used to ensure only a single thread can connect + /// and lazy initialize the at any given time. + /// + private readonly object _connectAndLazySemaphoreInitLock = new object(); + /// /// Holds metadata about session messages. /// @@ -192,6 +198,10 @@ namespace Renci.SshNet private SemaphoreLight _sessionSemaphore; + private bool _isDisconnectMessageSent; + + private uint _nextChannelNumber; + /// /// Holds connection socket. /// @@ -209,7 +219,7 @@ namespace Renci.SshNet { if (_sessionSemaphore is null) { - lock (this) + lock (_connectAndLazySemaphoreInitLock) { _sessionSemaphore ??= new SemaphoreLight(ConnectionInfo.MaxSessions); } @@ -219,10 +229,6 @@ namespace Renci.SshNet } } - private bool _isDisconnectMessageSent; - - private uint _nextChannelNumber; - /// /// Gets the next channel number. /// @@ -235,7 +241,7 @@ namespace Renci.SshNet { uint result; - lock (this) + lock (_connectAndLazySemaphoreInitLock) { result = _nextChannelNumber++; } @@ -586,7 +592,7 @@ namespace Renci.SshNet return; } - lock (this) + lock (_connectAndLazySemaphoreInitLock) { // If connected don't connect again if (IsConnected) @@ -635,7 +641,7 @@ namespace Renci.SshNet // Start incoming request listener // ToDo: Make message pump async, to not consume a thread for every session - ThreadAbstraction.ExecuteThreadLongRunning(MessageListener); + _ = ThreadAbstraction.ExecuteThreadLongRunning(MessageListener); // Wait for key exchange to be completed WaitOnHandle(_keyExchangeCompletedWaitHandle); @@ -747,7 +753,7 @@ namespace Renci.SshNet // Start incoming request listener // ToDo: Make message pump async, to not consume a thread for every session - ThreadAbstraction.ExecuteThreadLongRunning(MessageListener); + _ = ThreadAbstraction.ExecuteThreadLongRunning(MessageListener); // Wait for key exchange to be completed WaitOnHandle(_keyExchangeCompletedWaitHandle); @@ -1257,8 +1263,8 @@ namespace Renci.SshNet var clientHash = _serverMac.ComputeHash(data, 0, data.Length - serverMacLength); var serverHash = data.Take(data.Length - serverMacLength, serverMacLength); - // TODO add IsEqualTo overload that takes left+right index and number of bytes to compare; - // TODO that way we can eliminate the extra allocation of the Take above + // TODO Add IsEqualTo overload that takes left+right index and number of bytes to compare. + // TODO That way we can eliminate the extra allocation of the Take above. if (!serverHash.IsEqualTo(clientHash)) { throw new SshConnectionException("MAC error", DisconnectReason.MacError); @@ -1269,10 +1275,10 @@ namespace Renci.SshNet { data = _serverDecompression.Decompress(data, messagePayloadOffset, messagePayloadLength); - // data now only contains the decompressed payload, and as such the offset is reset to zero + // Data now only contains the decompressed payload, and as such the offset is reset to zero messagePayloadOffset = 0; - // the length of the payload is now the complete decompressed content + // The length of the payload is now the complete decompressed content messagePayloadLength = data.Length; } @@ -1285,10 +1291,10 @@ namespace Renci.SshNet { var disconnectMessage = new DisconnectMessage(reasonCode, message); - // send the disconnect message, but ignore the outcome + // Send the disconnect message, but ignore the outcome _ = TrySendMessage(disconnectMessage); - // mark disconnect message sent regardless of whether the send sctually succeeded + // Mark disconnect message sent regardless of whether the send sctually succeeded _isDisconnectMessageSent = true; } @@ -1440,12 +1446,9 @@ namespace Renci.SshNet _serverDecompression = _keyExchange.CreateDecompressor(); // Dispose of old KeyExchange object as it is no longer needed. - if (_keyExchange != null) - { - _keyExchange.HostKeyReceived -= KeyExchange_HostKeyReceived; - _keyExchange.Dispose(); - _keyExchange = null; - } + _keyExchange.HostKeyReceived -= KeyExchange_HostKeyReceived; + _keyExchange.Dispose(); + _keyExchange = null; // Enable activated messages that are not key exchange related _sshMessageFactory.EnableActivatedMessages(); @@ -2059,6 +2062,13 @@ namespace Renci.SshNet get { return ConnectionInfo; } } + /// + /// Gets a that can be used to wait for the message listener loop to complete. + /// + /// + /// A that can be used to wait for the message listener loop to complete, or + /// when the session has not been connected. + /// WaitHandle ISession.MessageListenerCompleted { get { return _messageListenerCompleted; } @@ -2089,6 +2099,9 @@ namespace Renci.SshNet /// /// Creates a "forwarded-tcpip" SSH channel. /// + /// The number of the remote channel. + /// The window size of the remote channel. + /// The data packet size of the remote channel. /// /// A new "forwarded-tcpip" SSH channel. /// diff --git a/src/Renci.SshNet/Sftp/Flags.cs b/src/Renci.SshNet/Sftp/Flags.cs index 68ae5b10..6f0a0e6c 100644 --- a/src/Renci.SshNet/Sftp/Flags.cs +++ b/src/Renci.SshNet/Sftp/Flags.cs @@ -3,31 +3,44 @@ namespace Renci.SshNet.Sftp { [Flags] +#pragma warning disable S2344 // Enumeration type names should not have "Flags" or "Enum" suffixes +#pragma warning disable MA0062 // Non-flags enums should not be marked with "FlagsAttribute" internal enum Flags +#pragma warning restore MA0062 // Non-flags enums should not be marked with "FlagsAttribute" +#pragma warning restore S2344 // Enumeration type names should not have "Flags" or "Enum" suffixes { - None = 0x00000000, /// - /// SSH_FXF_READ + /// None. + /// + None = 0x00000000, + + /// + /// SSH_FXF_READ. /// Read = 0x00000001, + /// - /// SSH_FXF_WRITE + /// SSH_FXF_WRITE. /// Write = 0x00000002, + /// - /// SSH_FXF_APPEND + /// SSH_FXF_APPEND. /// Append = 0x00000004, + /// - /// SSH_FXF_CREAT + /// SSH_FXF_CREAT. /// CreateNewOrOpen = 0x00000008, + /// - /// SSH_FXF_TRUNC + /// SSH_FXF_TRUNC. /// Truncate = 0x00000010, + /// - /// SSH_FXF_EXCL + /// SSH_FXF_EXCL. /// CreateNew = 0x00000028 } diff --git a/src/Renci.SshNet/Sftp/ISftpFile.cs b/src/Renci.SshNet/Sftp/ISftpFile.cs index a74e8fbf..02dff721 100644 --- a/src/Renci.SshNet/Sftp/ISftpFile.cs +++ b/src/Renci.SshNet/Sftp/ISftpFile.cs @@ -156,7 +156,7 @@ namespace Renci.SshNet.Sftp /// Gets or sets a value indicating whether the owner can write into this file. /// /// - /// if owner can write into this file; otherwise, . + /// if owner can write into this file; otherwise, . /// bool OwnerCanWrite { get; set; } @@ -164,7 +164,7 @@ namespace Renci.SshNet.Sftp /// Gets or sets a value indicating whether the owner can execute this file. /// /// - /// if owner can execute this file; otherwise, . + /// if owner can execute this file; otherwise, . /// bool OwnerCanExecute { get; set; } @@ -172,7 +172,7 @@ namespace Renci.SshNet.Sftp /// Gets or sets a value indicating whether the group members can read from this file. /// /// - /// if group members can read from this file; otherwise, . + /// if group members can read from this file; otherwise, . /// bool GroupCanRead { get; set; } @@ -180,7 +180,7 @@ namespace Renci.SshNet.Sftp /// Gets or sets a value indicating whether the group members can write into this file. /// /// - /// if group members can write into this file; otherwise, . + /// if group members can write into this file; otherwise, . /// bool GroupCanWrite { get; set; } @@ -188,7 +188,7 @@ namespace Renci.SshNet.Sftp /// Gets or sets a value indicating whether the group members can execute this file. /// /// - /// if group members can execute this file; otherwise, . + /// if group members can execute this file; otherwise, . /// bool GroupCanExecute { get; set; } @@ -196,7 +196,7 @@ namespace Renci.SshNet.Sftp /// Gets or sets a value indicating whether the others can read from this file. /// /// - /// if others can read from this file; otherwise, . + /// if others can read from this file; otherwise, . /// bool OthersCanRead { get; set; } @@ -204,7 +204,7 @@ namespace Renci.SshNet.Sftp /// Gets or sets a value indicating whether the others can write into this file. /// /// - /// if others can write into this file; otherwise, . + /// if others can write into this file; otherwise, . /// bool OthersCanWrite { get; set; } @@ -212,7 +212,7 @@ namespace Renci.SshNet.Sftp /// Gets or sets a value indicating whether the others can execute this file. /// /// - /// if others can execute this file; otherwise, . + /// if others can execute this file; otherwise, . /// bool OthersCanExecute { get; set; } diff --git a/src/Renci.SshNet/Sftp/ISftpFileReader.cs b/src/Renci.SshNet/Sftp/ISftpFileReader.cs index 684cc418..823b2e23 100644 --- a/src/Renci.SshNet/Sftp/ISftpFileReader.cs +++ b/src/Renci.SshNet/Sftp/ISftpFileReader.cs @@ -1,9 +1,23 @@ using System; +using Renci.SshNet.Common; + namespace Renci.SshNet.Sftp { + /// + /// Reads a given file. + /// internal interface ISftpFileReader : IDisposable { + /// + /// Reads a sequence of bytes from the current file and advances the position within the file by the number of bytes read. + /// + /// + /// The sequence of bytes read from the file, or a zero-length array if the end of the file + /// has been reached. + /// + /// The current is disposed. + /// Attempting to read beyond the end of the file. byte[] Read(); } } diff --git a/src/Renci.SshNet/Sftp/ISftpResponseFactory.cs b/src/Renci.SshNet/Sftp/ISftpResponseFactory.cs index afb440c1..ae57110f 100644 --- a/src/Renci.SshNet/Sftp/ISftpResponseFactory.cs +++ b/src/Renci.SshNet/Sftp/ISftpResponseFactory.cs @@ -2,8 +2,21 @@ namespace Renci.SshNet.Sftp { + /// + /// Represents a factory for creating SFTP response messages. + /// internal interface ISftpResponseFactory { + /// + /// Creates a SFTP response message for the specified protocol version and message type, and + /// with the specified . + /// + /// The protocol version. + /// The message type. + /// The . + /// + /// A . + /// SftpMessage Create(uint protocolVersion, byte messageType, Encoding encoding); } } diff --git a/src/Renci.SshNet/Sftp/ISftpSession.cs b/src/Renci.SshNet/Sftp/ISftpSession.cs index 31b091fa..ed9b7236 100644 --- a/src/Renci.SshNet/Sftp/ISftpSession.cs +++ b/src/Renci.SshNet/Sftp/ISftpSession.cs @@ -7,6 +7,9 @@ using Renci.SshNet.Sftp.Responses; namespace Renci.SshNet.Sftp { + /// + /// Represents an SFTP session. + /// internal interface ISftpSession : ISubsystemSession { /// @@ -40,25 +43,44 @@ namespace Renci.SshNet.Sftp /// string GetCanonicalPath(string path); + /// + /// Asynchronously resolves a given path into an absolute path on the server. + /// + /// The path to resolve. + /// The token to monitor for cancellation requests. + /// + /// A task that represents an asynchronous operation to resolve into + /// an absolute path. The value of its contains the absolute + /// path of the specified path. + /// Task GetCanonicalPathAsync(string path, CancellationToken cancellationToken); /// - /// Performs SSH_FXP_FSTAT request. + /// Asynchronously performs a SSH_FXP_FSTAT request. /// /// The handle. - /// if set to returns instead of throwing an exception. + /// If set to , is returned in case of an error. /// - /// File attributes + /// The file attributes. /// SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError); + /// + /// Asynchronously performs a SSH_FXP_FSTAT request. + /// + /// The handle. + /// The token to monitor for cancellation requests. + /// + /// A task the represents the asynchronous SSH_FXP_FSTAT request. The value of its + /// contains the file attributes of the specified handle. + /// Task RequestFStatAsync(byte[] handle, CancellationToken cancellationToken); /// /// Performs SSH_FXP_STAT request. /// /// The path. - /// if set to returns null instead of throwing an exception. + /// If set to , is returned in case of an error. /// /// File attributes. /// @@ -90,7 +112,7 @@ namespace Renci.SshNet.Sftp /// /// The path. /// - /// File attributes + /// File attributes. /// SftpFileAttributes RequestLStat(string path); @@ -122,14 +144,26 @@ namespace Renci.SshNet.Sftp void RequestMkDir(string path); /// - /// Performs SSH_FXP_OPEN request. + /// Performs a SSH_FXP_OPEN request. /// /// The path. /// The flags. - /// if set to returns instead of throwing an exception. - /// File handle. + /// If set to , is returned in case of an error. + /// + /// The file handle for the specified path. + /// byte[] RequestOpen(string path, Flags flags, bool nullOnError = false); + /// + /// Asynchronously performs a SSH_FXP_OPEN request. + /// + /// The path. + /// The flags. + /// The token to monitor for cancellation requests. + /// + /// A task the represents the asynchronous SSH_FXP_OPEN request. The value of its + /// contains the file handle of the specified path. + /// Task RequestOpenAsync(string path, Flags flags, CancellationToken cancellationToken); /// @@ -159,13 +193,24 @@ namespace Renci.SshNet.Sftp byte[] EndOpen(SftpOpenAsyncResult asyncResult); /// - /// Performs SSH_FXP_OPENDIR request. + /// Performs a SSH_FXP_OPENDIR request. /// /// The path. - /// if set to returns null instead of throwing an exception. - /// File handle. + /// If set to , is returned in case of an error. + /// + /// A file handle for the specified path. + /// byte[] RequestOpenDir(string path, bool nullOnError = false); + /// + /// Asynchronously performs a SSH_FXP_OPENDIR request. + /// + /// The path. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_OPENDIR request. The value of its + /// contains the handle of the specified path. + /// Task RequestOpenDirAsync(string path, CancellationToken cancellationToken); /// @@ -181,7 +226,7 @@ namespace Renci.SshNet.Sftp /// The handle. /// The offset. /// The length. - /// data array; null if EOF + /// data array; null if EOF. byte[] RequestRead(byte[] handle, ulong offset, uint length); /// @@ -211,15 +256,41 @@ namespace Renci.SshNet.Sftp /// is . byte[] EndRead(SftpReadAsyncResult asyncResult); + /// + /// Asynchronously performs a SSH_FXP_READ request. + /// + /// The handle to the file to read from. + /// The offset in the file to start reading from. + /// The number of bytes to read. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_READ request. The value of + /// its contains the data read from the file, or an empty + /// array when the end of the file is reached. + /// Task RequestReadAsync(byte[] handle, ulong offset, uint length, CancellationToken cancellationToken); /// - /// Performs SSH_FXP_READDIR request. + /// Performs a SSH_FXP_READDIR request. /// - /// The handle. - /// + /// The handle of the directory to read. + /// + /// A where the key is the name of a file in the directory + /// and the value is the of the file. + /// KeyValuePair[] RequestReadDir(byte[] handle); + /// + /// Performs a SSH_FXP_READDIR request. + /// + /// The handle of the directory to read. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_READDIR request. The value of its + /// contains a where the + /// key is the name of a file in the directory and the value is the + /// of the file. + /// Task[]> RequestReadDirAsync(byte[] handle, CancellationToken cancellationToken); /// @@ -244,20 +315,37 @@ namespace Renci.SshNet.Sftp string EndRealPath(SftpRealPathAsyncResult asyncResult); /// - /// Performs SSH_FXP_REMOVE request. + /// Performs a SSH_FXP_REMOVE request. /// /// The path. void RequestRemove(string path); + /// + /// Asynchronously performs a SSH_FXP_REMOVE request. + /// + /// The path. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_REMOVE request. + /// Task RequestRemoveAsync(string path, CancellationToken cancellationToken); /// - /// Performs SSH_FXP_RENAME request. + /// Performs a SSH_FXP_RENAME request. /// /// The old path. /// The new path. void RequestRename(string oldPath, string newPath); + /// + /// Asynchronously performs a SSH_FXP_RENAME request. + /// + /// The old path. + /// The new path. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_RENAME request. + /// Task RequestRenameAsync(string oldPath, string newPath, CancellationToken cancellationToken); /// @@ -274,13 +362,26 @@ namespace Renci.SshNet.Sftp void RequestSetStat(string path, SftpFileAttributes attributes); /// - /// Performs statvfs@openssh.com extended request. + /// Performs a statvfs@openssh.com extended request. /// /// The path. - /// if set to [null on error]. - /// + /// If set to , is returned in case of an error. + /// + /// The file system information for the specified path, or when + /// the request failed and is . + /// SftpFileSytemInformation RequestStatVfs(string path, bool nullOnError = false); + /// + /// Asynchronously performs a statvfs@openssh.com extended request. + /// + /// The path. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the statvfs@openssh.com extended request. The value of its + /// contains the file system information for the specified + /// path. + /// Task RequestStatVfsAsync(string path, CancellationToken cancellationToken); /// @@ -315,14 +416,34 @@ namespace Renci.SshNet.Sftp AutoResetEvent wait, Action writeCompleted = null); + /// + /// Asynchronouly performs a SSH_FXP_WRITE request. + /// + /// The handle. + /// The the zero-based offset (in bytes) relative to the beginning of the file that the write must start at. + /// The buffer holding the data to write. + /// the zero-based offset in at which to begin taking bytes to write. + /// The length (in bytes) of the data to write. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_WRITE request. + /// Task RequestWriteAsync(byte[] handle, ulong serverOffset, byte[] data, int offset, int length, CancellationToken cancellationToken); /// - /// Performs SSH_FXP_CLOSE request. + /// Performs a SSH_FXP_CLOSE request. /// /// The handle. void RequestClose(byte[] handle); + /// + /// Performs a SSH_FXP_CLOSE request. + /// + /// The handle. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_CLOSE request. + /// Task RequestCloseAsync(byte[] handle, CancellationToken cancellationToken); /// @@ -365,6 +486,18 @@ namespace Renci.SshNet.Sftp /// uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle); + /// + /// Creates an for reading the content of the file represented by a given . + /// + /// The handle of the file to read. + /// The SFTP session. + /// The maximum number of bytes to read with each chunk. + /// The maximum number of pending reads. + /// The size of the file or when the size could not be determined. + /// + /// An for reading the content of the file represented by the + /// specified . + /// ISftpFileReader CreateFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingReads, long? fileSize); } } diff --git a/src/Renci.SshNet/Sftp/Requests/SftpExtendedRequest.cs b/src/Renci.SshNet/Sftp/Requests/SftpExtendedRequest.cs index 8693929d..59da98af 100644 --- a/src/Renci.SshNet/Sftp/Requests/SftpExtendedRequest.cs +++ b/src/Renci.SshNet/Sftp/Requests/SftpExtendedRequest.cs @@ -15,7 +15,10 @@ namespace Renci.SshNet.Sftp.Requests public string Name { - get { return _name; } + get + { + return _name; + } private set { _name = value; @@ -52,4 +55,4 @@ namespace Renci.SshNet.Sftp.Requests WriteBinaryString(_nameBytes); } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet/Sftp/Responses/ExtendedReplies/StatVfsReplyInfo.cs b/src/Renci.SshNet/Sftp/Responses/ExtendedReplies/StatVfsReplyInfo.cs index 76876441..d7cecf8f 100644 --- a/src/Renci.SshNet/Sftp/Responses/ExtendedReplies/StatVfsReplyInfo.cs +++ b/src/Renci.SshNet/Sftp/Responses/ExtendedReplies/StatVfsReplyInfo.cs @@ -18,8 +18,7 @@ namespace Renci.SshNet.Sftp.Responses stream.ReadUInt64(), // AvailableNodes stream.ReadUInt64(), // Sid stream.ReadUInt64(), // Flags - stream.ReadUInt64() // MaxNameLenght - ); + stream.ReadUInt64()); // MaxNameLenght } } } diff --git a/src/Renci.SshNet/Sftp/Responses/SftpDataResponse.cs b/src/Renci.SshNet/Sftp/Responses/SftpDataResponse.cs index ce0d414c..04a7d4d8 100644 --- a/src/Renci.SshNet/Sftp/Responses/SftpDataResponse.cs +++ b/src/Renci.SshNet/Sftp/Responses/SftpDataResponse.cs @@ -17,7 +17,7 @@ protected override void LoadData() { base.LoadData(); - + Data = ReadBinary(); } diff --git a/src/Renci.SshNet/Sftp/Responses/SftpExtendedReplyResponse.cs b/src/Renci.SshNet/Sftp/Responses/SftpExtendedReplyResponse.cs index bbd41680..84fe93d3 100644 --- a/src/Renci.SshNet/Sftp/Responses/SftpExtendedReplyResponse.cs +++ b/src/Renci.SshNet/Sftp/Responses/SftpExtendedReplyResponse.cs @@ -12,7 +12,8 @@ { } - public T GetReply() where T : ExtendedReplyInfo, new() + public T GetReply() + where T : ExtendedReplyInfo, new() { var result = new T(); result.LoadData(DataStream); diff --git a/src/Renci.SshNet/Sftp/Responses/SftpHandleResponse.cs b/src/Renci.SshNet/Sftp/Responses/SftpHandleResponse.cs index 31da2e93..21af8f83 100644 --- a/src/Renci.SshNet/Sftp/Responses/SftpHandleResponse.cs +++ b/src/Renci.SshNet/Sftp/Responses/SftpHandleResponse.cs @@ -17,7 +17,7 @@ protected override void LoadData() { base.LoadData(); - + Handle = ReadBinary(); } diff --git a/src/Renci.SshNet/Sftp/Responses/SftpResponse.cs b/src/Renci.SshNet/Sftp/Responses/SftpResponse.cs index 3b784bf0..5e3ac145 100644 --- a/src/Renci.SshNet/Sftp/Responses/SftpResponse.cs +++ b/src/Renci.SshNet/Sftp/Responses/SftpResponse.cs @@ -14,7 +14,7 @@ protected override void LoadData() { base.LoadData(); - + ResponseId = ReadUInt32(); } diff --git a/src/Renci.SshNet/Sftp/SftpDownloadAsyncResult.cs b/src/Renci.SshNet/Sftp/SftpDownloadAsyncResult.cs index b2505b20..9e706d9e 100644 --- a/src/Renci.SshNet/Sftp/SftpDownloadAsyncResult.cs +++ b/src/Renci.SshNet/Sftp/SftpDownloadAsyncResult.cs @@ -6,7 +6,7 @@ namespace Renci.SshNet.Sftp /// /// Encapsulates the results of an asynchronous download operation. /// - public class SftpDownloadAsyncResult : AsyncResult + public class SftpDownloadAsyncResult : AsyncResult { /// /// Gets or sets a value indicating whether to cancel asynchronous download operation. diff --git a/src/Renci.SshNet/Sftp/SftpFileAttributes.cs b/src/Renci.SshNet/Sftp/SftpFileAttributes.cs index 824a3a0f..2f32b479 100644 --- a/src/Renci.SshNet/Sftp/SftpFileAttributes.cs +++ b/src/Renci.SshNet/Sftp/SftpFileAttributes.cs @@ -13,6 +13,7 @@ namespace Renci.SshNet.Sftp public class SftpFileAttributes { #pragma warning disable IDE1006 // Naming Styles +#pragma warning disable SA1310 // Field names should not contain underscore private const uint S_IFMT = 0xF000; // bitmask for the file type bitfields private const uint S_IFSOCK = 0xC000; // socket private const uint S_IFLNK = 0xA000; // symbolic link @@ -33,6 +34,7 @@ namespace Renci.SshNet.Sftp private const uint S_IROTH = 0x0004; // others have read permission private const uint S_IWOTH = 0x0002; // others have write permission private const uint S_IXOTH = 0x0001; // others have execute permission +#pragma warning restore SA1310 // Field names should not contain underscore #pragma warning restore IDE1006 // Naming Styles private readonly DateTime _originalLastAccessTimeUtc; @@ -165,7 +167,7 @@ namespace Renci.SshNet.Sftp /// Gets a value indicating whether file represents a socket. /// /// - /// if file represents a socket; otherwise, . + /// if file represents a socket; otherwise, . /// public bool IsSocket { get; private set; } @@ -477,7 +479,7 @@ namespace Renci.SshNet.Sftp var modeBytes = mode.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0').ToCharArray(); - var permission = (modeBytes[0] & 0x0F) * 8 * 8 + (modeBytes[1] & 0x0F) * 8 + (modeBytes[2] & 0x0F); + var permission = ((modeBytes[0] & 0x0F) * 8 * 8) + ((modeBytes[1] & 0x0F) * 8) + (modeBytes[2] & 0x0F); OwnerCanRead = (permission & S_IRUSR) == S_IRUSR; OwnerCanWrite = (permission & S_IWUSR) == S_IWUSR; @@ -500,79 +502,88 @@ namespace Renci.SshNet.Sftp /// public byte[] GetBytes() { - var stream = new SshDataStream(4); - - uint flag = 0; - - if (IsSizeChanged && IsRegularFile) + using (var stream = new SshDataStream(4)) { - flag |= 0x00000001; - } + uint flag = 0; - if (IsUserIdChanged || IsGroupIdChanged) - { - flag |= 0x00000002; - } - - if (IsPermissionsChanged) - { - flag |= 0x00000004; - } - - if (IsLastAccessTimeChanged || IsLastWriteTimeChanged) - { - flag |= 0x00000008; - } - - if (IsExtensionsChanged) - { - flag |= 0x80000000; - } - - stream.Write(flag); - - if (IsSizeChanged && IsRegularFile) - { - stream.Write((ulong) Size); - } - - if (IsUserIdChanged || IsGroupIdChanged) - { - stream.Write((uint) UserId); - stream.Write((uint) GroupId); - } - - if (IsPermissionsChanged) - { - stream.Write(Permissions); - } - - if (IsLastAccessTimeChanged || IsLastWriteTimeChanged) - { - var time = (uint)(LastAccessTimeUtc.ToFileTimeUtc() / 10000000 - 11644473600); - stream.Write(time); - time = (uint)(LastWriteTimeUtc.ToFileTimeUtc() / 10000000 - 11644473600); - stream.Write(time); - } - - if (IsExtensionsChanged) - { - foreach (var item in Extensions) + if (IsSizeChanged && IsRegularFile) { - // TODO: we write as ASCII but read as UTF8 !!! - - stream.Write(item.Key, SshData.Ascii); - stream.Write(item.Value, SshData.Ascii); + flag |= 0x00000001; } - } - return stream.ToArray(); + if (IsUserIdChanged || IsGroupIdChanged) + { + flag |= 0x00000002; + } + + if (IsPermissionsChanged) + { + flag |= 0x00000004; + } + + if (IsLastAccessTimeChanged || IsLastWriteTimeChanged) + { + flag |= 0x00000008; + } + + if (IsExtensionsChanged) + { + flag |= 0x80000000; + } + + stream.Write(flag); + + if (IsSizeChanged && IsRegularFile) + { + stream.Write((ulong) Size); + } + + if (IsUserIdChanged || IsGroupIdChanged) + { + stream.Write((uint) UserId); + stream.Write((uint) GroupId); + } + + if (IsPermissionsChanged) + { + stream.Write(Permissions); + } + + if (IsLastAccessTimeChanged || IsLastWriteTimeChanged) + { + var time = (uint) ((LastAccessTimeUtc.ToFileTimeUtc() / 10000000) - 11644473600); + stream.Write(time); + time = (uint) ((LastWriteTimeUtc.ToFileTimeUtc() / 10000000) - 11644473600); + stream.Write(time); + } + + if (IsExtensionsChanged) + { + foreach (var item in Extensions) + { + /* + * TODO: we write as ASCII but read as UTF8 !!! + */ + + stream.Write(item.Key, SshData.Ascii); + stream.Write(item.Value, SshData.Ascii); + } + } + + return stream.ToArray(); + } } internal static readonly SftpFileAttributes Empty = new SftpFileAttributes(); internal static SftpFileAttributes FromBytes(SshDataStream stream) { + const uint SSH_FILEXFER_ATTR_SIZE = 0x00000001; + const uint SSH_FILEXFER_ATTR_UIDGID = 0x00000002; + const uint SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004; + const uint SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008; + const uint SSH_FILEXFER_ATTR_EXTENDED = 0x80000000; + var flag = stream.ReadUInt32(); long size = -1; @@ -583,24 +594,24 @@ namespace Renci.SshNet.Sftp DateTime modifyTime; Dictionary extensions = null; - if ((flag & 0x00000001) == 0x00000001) // SSH_FILEXFER_ATTR_SIZE + if ((flag & SSH_FILEXFER_ATTR_SIZE) == SSH_FILEXFER_ATTR_SIZE) { size = (long) stream.ReadUInt64(); } - if ((flag & 0x00000002) == 0x00000002) // SSH_FILEXFER_ATTR_UIDGID + if ((flag & SSH_FILEXFER_ATTR_UIDGID) == SSH_FILEXFER_ATTR_UIDGID) { userId = (int) stream.ReadUInt32(); groupId = (int) stream.ReadUInt32(); } - if ((flag & 0x00000004) == 0x00000004) // SSH_FILEXFER_ATTR_PERMISSIONS + if ((flag & SSH_FILEXFER_ATTR_PERMISSIONS) == SSH_FILEXFER_ATTR_PERMISSIONS) { permissions = stream.ReadUInt32(); } - if ((flag & 0x00000008) == 0x00000008) // SSH_FILEXFER_ATTR_ACMODTIME + if ((flag & SSH_FILEXFER_ATTR_ACMODTIME) == SSH_FILEXFER_ATTR_ACMODTIME) { // The incoming times are "Unix times", so they're already in UTC. We need to preserve that // to avoid losing information in a local time conversion during the "fall back" hour in DST. @@ -615,7 +626,7 @@ namespace Renci.SshNet.Sftp modifyTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); } - if ((flag & 0x80000000) == 0x80000000) // SSH_FILEXFER_ATTR_EXTENDED + if ((flag & SSH_FILEXFER_ATTR_EXTENDED) == SSH_FILEXFER_ATTR_EXTENDED) { var extendedCount = (int) stream.ReadUInt32(); extensions = new Dictionary(extendedCount); diff --git a/src/Renci.SshNet/Sftp/SftpFileReader.cs b/src/Renci.SshNet/Sftp/SftpFileReader.cs index e10107b1..af32f87a 100644 --- a/src/Renci.SshNet/Sftp/SftpFileReader.cs +++ b/src/Renci.SshNet/Sftp/SftpFileReader.cs @@ -74,10 +74,14 @@ namespace Renci.SshNet.Sftp public byte[] Read() { +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_disposingOrDisposed, this); +#else if (_disposingOrDisposed) { throw new ObjectDisposedException(GetType().FullName); } +#endif // NET7_0_OR_GREATER if (_exception is not null) { @@ -168,7 +172,9 @@ namespace Renci.SshNet.Sftp var bytesToCatchUp = nextChunk.Offset - _offset; - // TODO: break loop and interrupt blocking wait in case of exception + /* + * TODO: break loop and interrupt blocking wait in case of exception + */ var read = _sftpSession.RequestRead(_handle, _offset, (uint) bytesToCatchUp); if (read.Length == 0) diff --git a/src/Renci.SshNet/Sftp/SftpFileStream.cs b/src/Renci.SshNet/Sftp/SftpFileStream.cs index be1e756a..03b9e088 100644 --- a/src/Renci.SshNet/Sftp/SftpFileStream.cs +++ b/src/Renci.SshNet/Sftp/SftpFileStream.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -12,7 +13,9 @@ namespace Renci.SshNet.Sftp /// Exposes a around a remote SFTP file, supporting both synchronous and asynchronous read and write operations. /// /// +#pragma warning disable CA1844 // Provide memory-based overrides of async methods when subclassing 'Stream' public class SftpFileStream : Stream +#pragma warning restore CA1844 // Provide memory-based overrides of async methods when subclassing 'Stream' { private readonly object _lock = new object(); private readonly int _readBufferSize; @@ -181,9 +184,9 @@ namespace Renci.SshNet.Sftp Name = path; _session = session; - _canRead = (access & FileAccess.Read) != 0; + _canRead = (access & FileAccess.Read) == FileAccess.Read; _canSeek = true; - _canWrite = (access & FileAccess.Write) != 0; + _canWrite = (access & FileAccess.Write) == FileAccess.Write; _handle = handle; @@ -221,9 +224,9 @@ namespace Renci.SshNet.Sftp // Initialize the object state. _session = session; - _canRead = (access & FileAccess.Read) != 0; + _canRead = (access & FileAccess.Read) == FileAccess.Read; _canSeek = true; - _canWrite = (access & FileAccess.Write) != 0; + _canWrite = (access & FileAccess.Write) == FileAccess.Write; var flags = Flags.None; @@ -243,20 +246,25 @@ namespace Renci.SshNet.Sftp throw new ArgumentOutOfRangeException(nameof(access)); } - if ((access & FileAccess.Read) != 0 && mode == FileMode.Append) + if ((access & FileAccess.Read) == FileAccess.Read && mode == FileMode.Append) { - throw new ArgumentException(string.Format("{0} mode can be requested only when combined with write-only access.", mode.ToString("G"))); + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "{0} mode can be requested only when combined with write-only access.", + mode.ToString("G")), + nameof(mode)); } - if ((access & FileAccess.Write) == 0) + if ((access & FileAccess.Write) != FileAccess.Write) { if (mode is FileMode.Create or FileMode.CreateNew or FileMode.Truncate or FileMode.Append) { - throw new ArgumentException(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), mode, nameof(FileAccess), - access)); + access), + nameof(mode)); } } @@ -345,20 +353,25 @@ namespace Renci.SshNet.Sftp throw new ArgumentOutOfRangeException(nameof(access)); } - if ((access & FileAccess.Read) != 0 && mode == FileMode.Append) + if ((access & FileAccess.Read) == FileAccess.Read && mode == FileMode.Append) { - throw new ArgumentException(string.Format("{0} mode can be requested only when combined with write-only access.", mode.ToString("G"))); + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "{0} mode can be requested only when combined with write-only access.", + mode.ToString("G")), + nameof(mode)); } - if ((access & FileAccess.Write) == 0) + if ((access & FileAccess.Write) != FileAccess.Write) { if (mode is FileMode.Create or FileMode.CreateNew or FileMode.Truncate or FileMode.Append) { - throw new ArgumentException(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), mode, nameof(FileAccess), - access)); + access), + nameof(mode)); } } @@ -1318,10 +1331,14 @@ namespace Renci.SshNet.Sftp private void CheckSessionIsOpen() { +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_session is null, this); +#else if (_session is null) { throw new ObjectDisposedException(GetType().FullName); } +#endif // NET7_0_OR_GREATER if (!_session.IsOpen) { diff --git a/src/Renci.SshNet/Sftp/SftpFileSystemInformation.cs b/src/Renci.SshNet/Sftp/SftpFileSystemInformation.cs index 61f32e1d..89d24a72 100644 --- a/src/Renci.SshNet/Sftp/SftpFileSystemInformation.cs +++ b/src/Renci.SshNet/Sftp/SftpFileSystemInformation.cs @@ -5,10 +5,14 @@ namespace Renci.SshNet.Sftp /// /// Contains File system information exposed by statvfs@openssh.com request. /// +#pragma warning disable SA1649 // File name should match first type name public class SftpFileSytemInformation +#pragma warning restore SA1649 // File name should match first type name { +#pragma warning disable SA1310 // Field names should not contain underscore internal const ulong SSH_FXE_STATVFS_ST_RDONLY = 0x1; internal const ulong SSH_FXE_STATVFS_ST_NOSUID = 0x2; +#pragma warning restore SA1310 // Field names should not contain underscore private readonly ulong _flag; @@ -99,7 +103,7 @@ namespace Renci.SshNet.Sftp /// Gets a value indicating whether [supports set uid]. /// /// - /// if [supports set uid]; otherwise, . + /// if [supports set uid]; otherwise, . /// public bool SupportsSetUid { @@ -158,4 +162,4 @@ namespace Renci.SshNet.Sftp stream.Write(MaxNameLenght); } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet/Sftp/SftpSession.cs b/src/Renci.SshNet/Sftp/SftpSession.cs index efca5d06..0fec9223 100644 --- a/src/Renci.SshNet/Sftp/SftpSession.cs +++ b/src/Renci.SshNet/Sftp/SftpSession.cs @@ -11,6 +11,9 @@ using Renci.SshNet.Sftp.Responses; namespace Renci.SshNet.Sftp { + /// + /// Represents an SFTP session. + /// internal sealed class SftpSession : SubsystemSession, ISftpSession { internal const int MaximumSupportedVersion = 3; @@ -114,14 +117,22 @@ namespace Renci.SshNet.Sftp if (fullPath.EndsWith("/.", StringComparison.OrdinalIgnoreCase) || fullPath.EndsWith("/..", StringComparison.OrdinalIgnoreCase) || fullPath.Equals("/", StringComparison.OrdinalIgnoreCase) || +#if NET || NETSTANDARD2_1_OR_GREATER + fullPath.IndexOf('/', StringComparison.OrdinalIgnoreCase) < 0) +#else fullPath.IndexOf('/') < 0) +#endif // NET || NETSTANDARD2_1_OR_GREATER { return fullPath; } var pathParts = fullPath.Split('/'); +#if NET || NETSTANDARD2_1_OR_GREATER + var partialFullPath = string.Join('/', pathParts, 0, pathParts.Length - 1); +#else var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1); +#endif // NET || NETSTANDARD2_1_OR_GREATER if (string.IsNullOrEmpty(partialFullPath)) { @@ -149,6 +160,14 @@ namespace Renci.SshNet.Sftp return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", canonizedPath, slash, pathParts[pathParts.Length - 1]); } + /// + /// Asynchronously resolves a given path into an absolute path on the server. + /// + /// The path to resolve. + /// The token to monitor for cancellation requests. + /// + /// A task representing the absolute path. + /// public async Task GetCanonicalPathAsync(string path, CancellationToken cancellationToken) { var fullPath = GetFullRemotePath(path); @@ -169,14 +188,22 @@ namespace Renci.SshNet.Sftp if (fullPath.EndsWith("/.", StringComparison.Ordinal) || fullPath.EndsWith("/..", StringComparison.Ordinal) || fullPath.Equals("/", StringComparison.Ordinal) || +#if NET || NETSTANDARD2_1_OR_GREATER + fullPath.IndexOf('/', StringComparison.Ordinal) < 0) +#else fullPath.IndexOf('/') < 0) +#endif // NET || NETSTANDARD2_1_OR_GREATER { return fullPath; } var pathParts = fullPath.Split('/'); - var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1); +#if NET || NETSTANDARD2_1_OR_GREATER + var partialFullPath = string.Join('/', pathParts); +#else + var partialFullPath = string.Join("/", pathParts); +#endif // NET || NETSTANDARD2_1_OR_GREATER if (string.IsNullOrEmpty(partialFullPath)) { @@ -204,6 +231,18 @@ namespace Renci.SshNet.Sftp return canonizedPath + slash + pathParts[pathParts.Length - 1]; } + /// + /// Creates an for reading the content of the file represented by a given . + /// + /// The handle of the file to read. + /// The SFTP session. + /// The maximum number of bytes to read with each chunk. + /// The maximum number of pending reads. + /// The size of the file or when the size could not be determined. + /// + /// An for reading the content of the file represented by the + /// specified . + /// public ISftpFileReader CreateFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingReads, long? fileSize) { return new SftpFileReader(handle, sftpSession, chunkSize, maxPendingReads, fileSize); @@ -224,6 +263,7 @@ namespace Renci.SshNet.Sftp fullPath = WorkingDirectory + '/' + path; } } + return fullPath; } @@ -417,7 +457,7 @@ namespace Renci.SshNet.Sftp /// /// The path. /// The flags. - /// if set to returns instead of throwing an exception. + /// If set to returns instead of throwing an exception. /// File handle. public byte[] RequestOpen(string path, Flags flags, bool nullOnError = false) { @@ -432,44 +472,61 @@ namespace Renci.SshNet.Sftp _encoding, flags, response => - { - handle = response.Handle; - _ = wait.Set(); - }, + { + handle = response.Handle; + _ = wait.Set(); + }, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (!nullOnError && exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (!nullOnError && exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } +#pragma warning restore CA1508 // Avoid dead conditional code return handle; } + /// + /// Asynchronously performs a SSH_FXP_OPEN request. + /// + /// The path. + /// The flags. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_OPEN request. The value of its + /// contains the file handle of the specified path. + /// public async Task RequestOpenAsync(string path, Flags flags, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register((s) => ((TaskCompletionSource) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { SendRequest(new SftpOpenRequest(ProtocolVersion, - NextRequestId, - path, - _encoding, - flags, - response => tcs.TrySetResult(response.Handle), - response => tcs.TrySetException(GetSftpException(response)))); + NextRequestId, + path, + _encoding, + flags, + response => tcs.TrySetResult(response.Handle), + response => tcs.TrySetException(GetSftpException(response)))); return await tcs.Task.ConfigureAwait(false); } @@ -495,13 +552,13 @@ namespace Renci.SshNet.Sftp _encoding, flags, response => - { - asyncResult.SetAsCompleted(response.Handle, completedSynchronously: false); - }, + { + asyncResult.SetAsCompleted(response.Handle, completedSynchronously: false); + }, response => - { - asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false); - }); + { + asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false); + }); SendRequest(request); @@ -558,22 +615,32 @@ namespace Renci.SshNet.Sftp NextRequestId, handle, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } } + /// + /// Performs a SSH_FXP_CLOSE request. + /// + /// The handle. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_CLOSE request. + /// public async Task RequestCloseAsync(byte[] handle, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -582,21 +649,25 @@ namespace Renci.SshNet.Sftp NextRequestId, handle, response => + { + if (response.StatusCode == StatusCodes.Ok) { - if (response.StatusCode == StatusCodes.Ok) - { - _ = tcs.TrySetResult(true); - } - else - { - _ = tcs.TrySetException(GetSftpException(response)); - } - })); + _ = tcs.TrySetResult(true); + } + else + { + _ = tcs.TrySetException(GetSftpException(response)); + } + })); // Only check for cancellation after the SftpCloseRequest was sent cancellationToken.ThrowIfCancellationRequested(); - using (cancellationToken.Register((s) => ((TaskCompletionSource) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { _ = await tcs.Task.ConfigureAwait(false); } @@ -619,9 +690,9 @@ namespace Renci.SshNet.Sftp NextRequestId, handle, response => - { - asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false); - }); + { + asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false); + }); SendRequest(request); return asyncResult; @@ -740,7 +811,9 @@ namespace Renci.SshNet.Sftp /// The handle. /// The offset. /// The length. - /// data array; null if EOF + /// + /// The data that was read, or an empty array when the end of the file was reached. + /// public byte[] RequestRead(byte[] handle, ulong offset, uint length) { SshException exception = null; @@ -755,30 +828,32 @@ namespace Renci.SshNet.Sftp offset, length, response => - { - data = response.Data; - _ = wait.Set(); - }, + { + data = response.Data; + _ = wait.Set(); + }, response => + { + if (response.StatusCode != StatusCodes.Eof) { - if (response.StatusCode != StatusCodes.Eof) - { - exception = GetSftpException(response); - } - else - { - data = Array.Empty(); - } + exception = GetSftpException(response); + } + else + { + data = Array.Empty(); + } - _ = wait.Set(); - }); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -786,13 +861,29 @@ namespace Renci.SshNet.Sftp return data; } + /// + /// Asynchronously performs a SSH_FXP_READ request. + /// + /// The handle to the file to read from. + /// The offset in the file to start reading from. + /// The number of bytes to read. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_READ request. The value of + /// its contains the data read from the file, or an empty + /// array when the end of the file is reached. + /// public async Task RequestReadAsync(byte[] handle, ulong offset, uint length, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register((s) => ((TaskCompletionSource) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { SendRequest(new SftpReadRequest(ProtocolVersion, NextRequestId, @@ -844,36 +935,54 @@ namespace Renci.SshNet.Sftp offset, length, response => - { - writeCompleted?.Invoke(response); + { + writeCompleted?.Invoke(response); - exception = GetSftpException(response); - if (wait != null) - { - _ = wait.Set(); - } - }); + exception = GetSftpException(response); + if (wait != null) + { + _ = wait.Set(); + } + }); SendRequest(request); - if (wait != null) + if (wait is not null) { WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } } + /// + /// Asynchronouly performs a SSH_FXP_WRITE request. + /// + /// The handle. + /// The the zero-based offset (in bytes) relative to the beginning of the file that the write must start at. + /// The buffer holding the data to write. + /// the zero-based offset in at which to begin taking bytes to write. + /// The length (in bytes) of the data to write. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_WRITE request. + /// public async Task RequestWriteAsync(byte[] handle, ulong serverOffset, byte[] data, int offset, int length, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register((s) => ((TaskCompletionSource) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { SendRequest(new SftpWriteRequest(ProtocolVersion, NextRequestId, @@ -883,16 +992,16 @@ namespace Renci.SshNet.Sftp offset, length, response => + { + if (response.StatusCode == StatusCodes.Ok) { - if (response.StatusCode == StatusCodes.Ok) - { - _ = tcs.TrySetResult(true); - } - else - { - _ = tcs.TrySetException(GetSftpException(response)); - } - })); + _ = tcs.TrySetResult(true); + } + else + { + _ = tcs.TrySetException(GetSftpException(response)); + } + })); _ = await tcs.Task.ConfigureAwait(false); } @@ -917,22 +1026,24 @@ namespace Renci.SshNet.Sftp path, _encoding, response => - { - attributes = response.Attributes; - _ = wait.Set(); - }, + { + attributes = response.Attributes; + _ = wait.Set(); + }, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -958,13 +1069,13 @@ namespace Renci.SshNet.Sftp path, _encoding, response => - { - asyncResult.SetAsCompleted(response.Attributes, completedSynchronously: false); - }, + { + asyncResult.SetAsCompleted(response.Attributes, completedSynchronously: false); + }, response => - { - asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false); - }); + { + asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false); + }); SendRequest(request); return asyncResult; @@ -1006,7 +1117,7 @@ namespace Renci.SshNet.Sftp /// Performs SSH_FXP_FSTAT request. /// /// The handle. - /// if set to returns instead of throwing an exception. + /// If set to , returns instead of throwing an exception. /// /// File attributes. /// @@ -1021,22 +1132,24 @@ namespace Renci.SshNet.Sftp NextRequestId, handle, response => - { - attributes = response.Attributes; - _ = wait.Set(); - }, + { + attributes = response.Attributes; + _ = wait.Set(); + }, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null && !nullOnError) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (!nullOnError && exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1044,13 +1157,26 @@ namespace Renci.SshNet.Sftp return attributes; } + /// + /// Asynchronously performs a SSH_FXP_FSTAT request. + /// + /// The handle. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_FSTAT request. The value of its + /// contains the file attributes of the specified handle. + /// public async Task RequestFStatAsync(byte[] handle, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register((s) => ((TaskCompletionSource) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { SendRequest(new SftpFStatRequest(ProtocolVersion, NextRequestId, @@ -1079,17 +1205,19 @@ namespace Renci.SshNet.Sftp _encoding, attributes, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1111,17 +1239,19 @@ namespace Renci.SshNet.Sftp handle, attributes, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1131,7 +1261,7 @@ namespace Renci.SshNet.Sftp /// Performs SSH_FXP_OPENDIR request. /// /// The path. - /// if set to returns instead of throwing an exception. + /// If set to , returns instead of throwing an exception. /// File handle. public byte[] RequestOpenDir(string path, bool nullOnError = false) { @@ -1146,22 +1276,24 @@ namespace Renci.SshNet.Sftp path, _encoding, response => - { - handle = response.Handle; - _ = wait.Set(); - }, + { + handle = response.Handle; + _ = wait.Set(); + }, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (!nullOnError && exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (!nullOnError && exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1169,13 +1301,26 @@ namespace Renci.SshNet.Sftp return handle; } + /// + /// Asynchronously performs a SSH_FXP_OPENDIR request. + /// + /// The path. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_OPENDIR request. The value of its + /// contains the handle of the specified path. + /// public async Task RequestOpenDirAsync(string path, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register((s) => ((TaskCompletionSource) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { SendRequest(new SftpOpenDirRequest(ProtocolVersion, NextRequestId, @@ -1191,8 +1336,11 @@ namespace Renci.SshNet.Sftp /// /// Performs SSH_FXP_READDIR request. /// - /// The handle. - /// + /// The handle of the directory to read. + /// + /// A where the key is the name of a file in + /// the directory and the value is the of the file. + /// public KeyValuePair[] RequestReadDir(byte[] handle) { SshException exception = null; @@ -1205,26 +1353,28 @@ namespace Renci.SshNet.Sftp NextRequestId, handle, response => - { - result = response.Files; - _ = wait.Set(); - }, + { + result = response.Files; + _ = wait.Set(); + }, response => + { + if (response.StatusCode != StatusCodes.Eof) { - if (response.StatusCode != StatusCodes.Eof) - { - exception = GetSftpException(response); - } + exception = GetSftpException(response); + } - _ = wait.Set(); - }); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1232,29 +1382,44 @@ namespace Renci.SshNet.Sftp return result; } + /// + /// Performs a SSH_FXP_READDIR request. + /// + /// The handle of the directory to read. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_READDIR request. The value of its + /// contains a where the + /// key is the name of a file in the directory and the value is the + /// of the file. + /// public async Task[]> RequestReadDirAsync(byte[] handle, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource[]>(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register((s) => ((TaskCompletionSource[]>) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource[]>) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource[]>) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { SendRequest(new SftpReadDirRequest(ProtocolVersion, NextRequestId, handle, response => tcs.TrySetResult(response.Files), response => + { + if (response.StatusCode == StatusCodes.Eof) { - if (response.StatusCode == StatusCodes.Eof) - { - _ = tcs.TrySetResult(null); - } - else - { - _ = tcs.TrySetException(GetSftpException(response)); - } - })); + _ = tcs.TrySetResult(null); + } + else + { + _ = tcs.TrySetException(GetSftpException(response)); + } + })); return await tcs.Task.ConfigureAwait(false); } @@ -1275,45 +1440,59 @@ namespace Renci.SshNet.Sftp path, _encoding, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } } + /// + /// Asynchronously performs a SSH_FXP_REMOVE request. + /// + /// The path. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_REMOVE request. + /// public async Task RequestRemoveAsync(string path, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register((s) => ((TaskCompletionSource) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { SendRequest(new SftpRemoveRequest(ProtocolVersion, NextRequestId, path, _encoding, response => + { + if (response.StatusCode == StatusCodes.Ok) { - if (response.StatusCode == StatusCodes.Ok) - { - _ = tcs.TrySetResult(true); - } - else - { - _ = tcs.TrySetException(GetSftpException(response)); - } - })); + _ = tcs.TrySetResult(true); + } + else + { + _ = tcs.TrySetException(GetSftpException(response)); + } + })); _ = await tcs.Task.ConfigureAwait(false); } @@ -1334,17 +1513,19 @@ namespace Renci.SshNet.Sftp path, _encoding, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1365,17 +1546,19 @@ namespace Renci.SshNet.Sftp path, _encoding, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1402,22 +1585,24 @@ namespace Renci.SshNet.Sftp path, _encoding, response => - { - result = response.Files; - _ = wait.Set(); - }, + { + result = response.Files; + _ = wait.Set(); + }, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (!nullOnError && exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (!nullOnError && exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1431,7 +1616,11 @@ namespace Renci.SshNet.Sftp var tcs = new TaskCompletionSource[]>(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register((s) => ((TaskCompletionSource[]>) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource[]>) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource[]>) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { SendRequest(new SftpRealPathRequest(ProtocolVersion, NextRequestId, @@ -1439,15 +1628,15 @@ namespace Renci.SshNet.Sftp _encoding, response => tcs.TrySetResult(response.Files), response => + { + if (nullOnError) { - if (nullOnError) - { - _ = tcs.TrySetResult(null); - } - else - { - _ = tcs.TrySetException(GetSftpException(response)); - } + _ = tcs.TrySetResult(null); + } + else + { + _ = tcs.TrySetException(GetSftpException(response)); + } })); return await tcs.Task.ConfigureAwait(false); @@ -1531,22 +1720,24 @@ namespace Renci.SshNet.Sftp path, _encoding, response => - { - attributes = response.Attributes; - _ = wait.Set(); - }, + { + attributes = response.Attributes; + _ = wait.Set(); + }, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (!nullOnError && exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (!nullOnError && exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1632,29 +1823,44 @@ namespace Renci.SshNet.Sftp newPath, _encoding, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } } + /// + /// Asynchronously performs a SSH_FXP_RENAME request. + /// + /// The old path. + /// The new path. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the asynchronous SSH_FXP_RENAME request. + /// public async Task RequestRenameAsync(string oldPath, string newPath, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register((s) => ((TaskCompletionSource) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { SendRequest(new SftpRenameRequest(ProtocolVersion, NextRequestId, @@ -1662,16 +1868,16 @@ namespace Renci.SshNet.Sftp newPath, _encoding, response => + { + if (response.StatusCode == StatusCodes.Ok) { - if (response.StatusCode == StatusCodes.Ok) - { - _ = tcs.TrySetResult(true); - } - else - { - _ = tcs.TrySetException(GetSftpException(response)); - } - })); + _ = tcs.TrySetResult(true); + } + else + { + _ = tcs.TrySetException(GetSftpException(response)); + } + })); _ = await tcs.Task.ConfigureAwait(false); } @@ -1681,8 +1887,11 @@ namespace Renci.SshNet.Sftp /// Performs SSH_FXP_READLINK request. /// /// The path. - /// if set to returns null instead of throwing an exception. - /// + /// if set to returns instead of throwing an exception. + /// + /// An array of where the key is the name of + /// a file and the value is the of the file. + /// internal KeyValuePair[] RequestReadLink(string path, bool nullOnError = false) { if (ProtocolVersion < 3) @@ -1701,22 +1910,24 @@ namespace Renci.SshNet.Sftp path, _encoding, response => - { - result = response.Files; - _ = wait.Set(); - }, + { + result = response.Files; + _ = wait.Set(); + }, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (!nullOnError && exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (!nullOnError && exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1746,17 +1957,19 @@ namespace Renci.SshNet.Sftp targetpath, _encoding, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); SendRequest(request); WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1784,10 +1997,10 @@ namespace Renci.SshNet.Sftp newPath, _encoding, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); if (!_supportedExtensions.ContainsKey(request.Name)) { @@ -1799,7 +2012,9 @@ namespace Renci.SshNet.Sftp WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1831,15 +2046,15 @@ namespace Renci.SshNet.Sftp path, _encoding, response => - { - information = response.GetReply().Information; - _ = wait.Set(); - }, + { + information = response.GetReply().Information; + _ = wait.Set(); + }, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); if (!_supportedExtensions.ContainsKey(request.Name)) { @@ -1851,7 +2066,9 @@ namespace Renci.SshNet.Sftp WaitOnHandle(wait, OperationTimeout); } - if (!nullOnError && exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (!nullOnError && exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1859,6 +2076,16 @@ namespace Renci.SshNet.Sftp return information; } + /// + /// Asynchronously performs a statvfs@openssh.com extended request. + /// + /// The path. + /// The token to monitor for cancellation requests. + /// + /// A task that represents the statvfs@openssh.com extended request. The value of its + /// contains the file system information for the specified + /// path. + /// public async Task RequestStatVfsAsync(string path, CancellationToken cancellationToken) { if (ProtocolVersion < 3) @@ -1870,7 +2097,11 @@ namespace Renci.SshNet.Sftp var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register((s) => ((TaskCompletionSource) s).TrySetCanceled(), tcs, useSynchronizationContext: false)) +#if NET || NETSTANDARD2_1_OR_GREATER + await using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false)) +#else + using (cancellationToken.Register(s => ((TaskCompletionSource) s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { SendRequest(new StatVfsRequest(ProtocolVersion, NextRequestId, @@ -1909,15 +2140,15 @@ namespace Renci.SshNet.Sftp NextRequestId, handle, response => - { - information = response.GetReply().Information; - _ = wait.Set(); - }, + { + information = response.GetReply().Information; + _ = wait.Set(); + }, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); if (!_supportedExtensions.ContainsKey(request.Name)) { @@ -1929,7 +2160,9 @@ namespace Renci.SshNet.Sftp WaitOnHandle(wait, OperationTimeout); } - if (!nullOnError && exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (!nullOnError && exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -1958,10 +2191,10 @@ namespace Renci.SshNet.Sftp oldPath, newPath, response => - { - exception = GetSftpException(response); - _ = wait.Set(); - }); + { + exception = GetSftpException(response); + _ = wait.Set(); + }); if (!_supportedExtensions.ContainsKey(request.Name)) { @@ -1973,7 +2206,9 @@ namespace Renci.SshNet.Sftp WaitOnHandle(wait, OperationTimeout); } - if (exception != null) +#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed + if (exception is not null) +#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed { throw exception; } @@ -2033,7 +2268,7 @@ namespace Renci.SshNet.Sftp * WinSCP uses data length of 32739 bytes (total 32768 bytes; 32739 + 25 + 4 bytes for handle) */ - var lengthOfNonDataProtocolFields = 25u + (uint)handle.Length; + var lengthOfNonDataProtocolFields = 25u + (uint) handle.Length; var maximumPacketSize = Channel.RemotePacketSize; return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields; } @@ -2061,7 +2296,7 @@ namespace Renci.SshNet.Sftp lock (_requests) { _ = _requests.TryGetValue(response.ResponseId, out request); - if (request != null) + if (request is not null) { _ = _requests.Remove(response.ResponseId); } diff --git a/src/Renci.SshNet/Sftp/StatusCodes.cs b/src/Renci.SshNet/Sftp/StatusCodes.cs index 101b8c0b..12997a47 100644 --- a/src/Renci.SshNet/Sftp/StatusCodes.cs +++ b/src/Renci.SshNet/Sftp/StatusCodes.cs @@ -1,135 +1,165 @@ - -namespace Renci.SshNet.Sftp +namespace Renci.SshNet.Sftp { internal enum StatusCodes : uint { /// - /// SSH_FX_OK + /// SSH_FX_OK. /// Ok = 0, + /// - /// SSH_FX_EOF + /// SSH_FX_EOF. /// Eof = 1, + /// - /// SSH_FX_NO_SUCH_FILE + /// SSH_FX_NO_SUCH_FILE. /// NoSuchFile = 2, + /// - /// SSH_FX_PERMISSION_DENIED + /// SSH_FX_PERMISSION_DENIED. /// PermissionDenied = 3, + /// - /// SSH_FX_FAILURE + /// SSH_FX_FAILURE. /// Failure = 4, + /// - /// SSH_FX_BAD_MESSAGE + /// SSH_FX_BAD_MESSAGE. /// BadMessage = 5, + /// - /// SSH_FX_NO_CONNECTION + /// SSH_FX_NO_CONNECTION. /// NoConnection = 6, + /// - /// SSH_FX_CONNECTION_LOST + /// SSH_FX_CONNECTION_LOST. /// ConnectionLost = 7, + /// - /// SSH_FX_OP_UNSUPPORTED + /// SSH_FX_OP_UNSUPPORTED. /// OperationUnsupported = 8, + /// - /// SSH_FX_INVALID_HANDLE + /// SSH_FX_INVALID_HANDLE. /// InvalidHandle = 9, + /// - /// SSH_FX_NO_SUCH_PATH + /// SSH_FX_NO_SUCH_PATH. /// NoSuchPath = 10, + /// - /// SSH_FX_FILE_ALREADY_EXISTS + /// SSH_FX_FILE_ALREADY_EXISTS. /// FileAlreadyExists = 11, + /// - /// SSH_FX_WRITE_PROTECT + /// SSH_FX_WRITE_PROTECT. /// WriteProtect = 12, + /// - /// SSH_FX_NO_MEDIA + /// SSH_FX_NO_MEDIA. /// NoMedia = 13, + /// - /// SSH_FX_NO_SPACE_ON_FILESYSTEM + /// SSH_FX_NO_SPACE_ON_FILESYSTEM. /// NoSpaceOnFilesystem = 14, + /// - /// SSH_FX_QUOTA_EXCEEDED + /// SSH_FX_QUOTA_EXCEEDED. /// QuotaExceeded = 15, + /// - /// SSH_FX_UNKNOWN_PRINCIPAL + /// SSH_FX_UNKNOWN_PRINCIPAL. /// UnknownPrincipal = 16, + /// - /// SSH_FX_LOCK_CONFLICT + /// SSH_FX_LOCK_CONFLICT. /// LockConflict = 17, + /// - /// SSH_FX_DIR_NOT_EMPTY + /// SSH_FX_DIR_NOT_EMPTY. /// DirNotEmpty = 18, + /// - /// SSH_FX_NOT_A_DIRECTORY + /// SSH_FX_NOT_A_DIRECTORY. /// NotDirectory = 19, + /// - /// SSH_FX_INVALID_FILENAME + /// SSH_FX_INVALID_FILENAME. /// InvalidFilename = 20, + /// - /// SSH_FX_LINK_LOOP + /// SSH_FX_LINK_LOOP. /// LinkLoop = 21, + /// - /// SSH_FX_CANNOT_DELETE + /// SSH_FX_CANNOT_DELETE. /// CannotDelete = 22, + /// - /// SSH_FX_INVALID_PARAMETER + /// SSH_FX_INVALID_PARAMETER. /// InvalidParameter = 23, + /// - /// SSH_FX_FILE_IS_A_DIRECTORY + /// SSH_FX_FILE_IS_A_DIRECTORY. /// FileIsADirectory = 24, + /// - /// SSH_FX_BYTE_RANGE_LOCK_CONFLICT + /// SSH_FX_BYTE_RANGE_LOCK_CONFLICT. /// ByteRangeLockConflict = 25, + /// - /// SSH_FX_BYTE_RANGE_LOCK_REFUSED + /// SSH_FX_BYTE_RANGE_LOCK_REFUSED. /// ByteRangeLockRefused = 26, + /// - /// SSH_FX_DELETE_PENDING + /// SSH_FX_DELETE_PENDING. /// DeletePending = 27, + /// - /// SSH_FX_FILE_CORRUPT + /// SSH_FX_FILE_CORRUPT. /// FileCorrupt = 28, + /// - /// SSH_FX_OWNER_INVALID + /// SSH_FX_OWNER_INVALID. /// OwnerInvalid = 29, + /// - /// SSH_FX_GROUP_INVALID + /// SSH_FX_GROUP_INVALID. /// GroupInvalid = 30, + /// - /// SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK + /// SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK. /// - NoMatchingByteRangeLock = 31, + NoMatchingByteRangeLock = 31 } } diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs index 5467ceb9..aa30efad 100644 --- a/src/Renci.SshNet/SftpClient.cs +++ b/src/Renci.SshNet/SftpClient.cs @@ -1,16 +1,17 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Globalization; +using System.IO; using System.Net; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; +using System.Threading.Tasks; + using Renci.SshNet.Abstractions; using Renci.SshNet.Common; using Renci.SshNet.Sftp; -using System.Threading.Tasks; -using System.Runtime.CompilerServices; namespace Renci.SshNet { @@ -213,7 +214,7 @@ namespace Renci.SshNet /// Authentication username. /// Authentication private key file(s) . /// is . - /// is invalid. -or- is null or contains only whitespace characters. + /// is invalid. -or- is or contains only whitespace characters. /// is not within and . [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")] public SftpClient(string host, int port, string username, params IPrivateKeySource[] keyFiles) @@ -447,44 +448,6 @@ namespace Renci.SshNet RenameFile(oldPath, newPath, isPosix: false); } - /// - /// Asynchronously renames remote file from old path to new path. - /// - /// Path to the old file location. - /// Path to the new file location. - /// The to observe. - /// A that represents the asynchronous rename operation. - /// is . -or- or is . - /// Client is not connected. - /// Permission to rename the file was denied by the remote host. -or- A SSH command was denied by the server. - /// A SSH error where is the message from the remote host. - /// The method was called after the client was disposed. - public async Task RenameFileAsync(string oldPath, string newPath, CancellationToken cancellationToken) - { - CheckDisposed(); - - if (oldPath is null) - { - throw new ArgumentNullException(nameof(oldPath)); - } - - if (newPath is null) - { - throw new ArgumentNullException(nameof(newPath)); - } - - if (_sftpSession is null) - { - throw new SshConnectionException("Client not connected."); - } - - cancellationToken.ThrowIfCancellationRequested(); - - var oldFullPath = await _sftpSession.GetCanonicalPathAsync(oldPath, cancellationToken).ConfigureAwait(false); - var newFullPath = await _sftpSession.GetCanonicalPathAsync(newPath, cancellationToken).ConfigureAwait(false); - await _sftpSession.RequestRenameAsync(oldFullPath, newFullPath, cancellationToken).ConfigureAwait(false); - } - /// /// Renames remote file from old path to new path. /// @@ -529,6 +492,44 @@ namespace Renci.SshNet } } + /// + /// Asynchronously renames remote file from old path to new path. + /// + /// Path to the old file location. + /// Path to the new file location. + /// The to observe. + /// A that represents the asynchronous rename operation. + /// is . -or- or is . + /// Client is not connected. + /// Permission to rename the file was denied by the remote host. -or- A SSH command was denied by the server. + /// A SSH error where is the message from the remote host. + /// The method was called after the client was disposed. + public async Task RenameFileAsync(string oldPath, string newPath, CancellationToken cancellationToken) + { + CheckDisposed(); + + if (oldPath is null) + { + throw new ArgumentNullException(nameof(oldPath)); + } + + if (newPath is null) + { + throw new ArgumentNullException(nameof(newPath)); + } + + if (_sftpSession is null) + { + throw new SshConnectionException("Client not connected."); + } + + cancellationToken.ThrowIfCancellationRequested(); + + var oldFullPath = await _sftpSession.GetCanonicalPathAsync(oldPath, cancellationToken).ConfigureAwait(false); + var newFullPath = await _sftpSession.GetCanonicalPathAsync(newPath, cancellationToken).ConfigureAwait(false); + await _sftpSession.RequestRenameAsync(oldFullPath, newFullPath, cancellationToken).ConfigureAwait(false); + } + /// /// Creates a symbolic link from old path to new path. /// @@ -735,7 +736,7 @@ namespace Renci.SshNet } /// - /// Checks whether file or directory exists; + /// Checks whether file or directory exists. /// /// The path. /// @@ -762,22 +763,24 @@ namespace Renci.SshNet var fullPath = _sftpSession.GetCanonicalPath(path); - // using SSH_FXP_REALPATH is not an alternative as the SFTP specification has not always - // been clear on how the server should respond when the specified path is not present on - // the server: - // - // SSH 1 to 4: - // No mention of how the server should respond if the path is not present on the server. - // - // SSH 5: - // The server SHOULD fail the request if the path is not present on the server. - // - // SSH 6: - // Draft 06: The server SHOULD fail the request if the path is not present on the server. - // Draft 07 to 13: The server MUST NOT fail the request if the path does not exist. - // - // Note that SSH 6 (draft 06 and forward) allows for more control options, but we - // currently only support up to v3. + /* + * Using SSH_FXP_REALPATH is not an alternative as the SFTP specification has not always + * been clear on how the server should respond when the specified path is not present on + * the server: + * + * SSH 1 to 4: + * No mention of how the server should respond if the path is not present on the server. + * + * SSH 5: + * The server SHOULD fail the request if the path is not present on the server. + * + * SSH 6: + * Draft 06: The server SHOULD fail the request if the path is not present on the server. + * Draft 07 to 13: The server MUST NOT fail the request if the path does not exist. + * + * Note that SSH 6 (draft 06 and forward) allows for more control options, but we + * currently only support up to v3. + */ try { @@ -1233,12 +1236,12 @@ namespace Renci.SshNet /// /// The file to append the lines to. The file is created if it does not already exist. /// The lines to append to the file. - /// is -or- is . + /// is . -or- is . /// Client is not connected. /// The specified path is invalid, or its directory was not found on the remote host. /// The method was called after the client was disposed. /// - /// The characters are written to the file using UTF-8 encoding without a Byte-Order Mark (BOM) + /// The characters are written to the file using UTF-8 encoding without a byte-order mark (BOM). /// public void AppendAllLines(string path, IEnumerable contents) { @@ -1710,10 +1713,13 @@ namespace Renci.SshNet /// The method was called after the client was disposed. public string[] ReadAllLines(string path, Encoding encoding) { - // we use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size - // for the SftpFileStream; may want to revisit this later + /* + * We use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size + * for the SftpFileStream. We may want to revisit this later. + */ var lines = new List(); + using (var stream = new StreamReader(OpenRead(path), encoding)) { while (!stream.EndOfStream) @@ -1721,6 +1727,7 @@ namespace Renci.SshNet lines.Add(stream.ReadLine()); } } + return lines.ToArray(); } @@ -1752,8 +1759,10 @@ namespace Renci.SshNet /// The method was called after the client was disposed. public string ReadAllText(string path, Encoding encoding) { - // we use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size - // for the SftpFileStream; may want to revisit this later + /* + * We use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size + * for the SftpFileStream. We may want to revisit this later. + */ using (var stream = new StreamReader(OpenRead(path), encoding)) { @@ -2074,15 +2083,6 @@ namespace Renci.SshNet _sftpSession.RequestSetStat(fullPath, fileAttributes); } - // Please don't forget this when you implement these methods: is . - //public FileSecurity GetAccessControl(string path); - //public FileSecurity GetAccessControl(string path, AccessControlSections includeSections); - //public DateTime GetCreationTime(string path); - //public DateTime GetCreationTimeUtc(string path); - //public void SetAccessControl(string path, FileSecurity fileSecurity); - //public void SetCreationTime(string path, DateTime creationTime); - //public void SetCreationTimeUtc(string path, DateTime creationTimeUtc); - #endregion // File Methods #region SynchronizeDirectories @@ -2099,7 +2099,7 @@ namespace Renci.SshNet /// is . /// is or contains only whitespace. /// was not found on the remote host. - /// If a problem occurs while copying the file + /// If a problem occurs while copying the file. public IEnumerable SynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern) { if (sourcePath is null) @@ -2128,7 +2128,7 @@ namespace Renci.SshNet /// /// is . /// is or contains only whitespace. - /// If a problem occurs while copying the file + /// If a problem occurs while copying the file. public IAsyncResult BeginSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, AsyncCallback asyncCallback, object state) { if (sourcePath is null) @@ -2180,7 +2180,7 @@ namespace Renci.SshNet return ar.EndInvoke(); } - private IEnumerable InternalSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, SftpSynchronizeDirectoriesAsyncResult asynchResult) + private List InternalSynchronizeDirectories(string sourcePath, string destinationPath, string searchPattern, SftpSynchronizeDirectoriesAsyncResult asynchResult) { if (!Directory.Exists(sourcePath)) { @@ -2228,8 +2228,7 @@ namespace Renci.SshNet var isDifferent = true; if (destDict.TryGetValue(localFile.Name, out var remoteFile)) { - // TODO: Use md5 to detect a difference - //ltang: File exists at the destination => Using filesize to detect the difference + // File exists at the destination, use filesize to detect if there's a difference isDifferent = localFile.Length != remoteFile.Length; } @@ -2238,7 +2237,9 @@ namespace Renci.SshNet var remoteFileName = string.Format(CultureInfo.InvariantCulture, @"{0}/{1}", destinationPath, localFile.Name); try { +#pragma warning disable CA2000 // Dispose objects before losing scope; false positive using (var file = File.OpenRead(localFile.FullName)) +#pragma warning restore CA2000 // Dispose objects before losing scope; false positive { InternalUploadFile(file, remoteFileName, uploadFlag, asyncResult: null, uploadCallback: null); } @@ -2273,7 +2274,7 @@ namespace Renci.SshNet /// /// is . /// Client not connected. - private IEnumerable InternalListDirectory(string path, Action listCallback) + private List InternalListDirectory(string path, Action listCallback) { if (path is null) { @@ -2291,7 +2292,11 @@ namespace Renci.SshNet var basePath = fullPath; - if (!basePath.EndsWith("/")) +#if NET || NETSTANDARD2_1_OR_GREATER + if (!basePath.EndsWith('/')) +#else + if (!basePath.EndsWith("/", StringComparison.Ordinal)) +#endif // NET || NETSTANDARD2_1_OR_GREATER { basePath = string.Format("{0}/", fullPath); } @@ -2309,10 +2314,10 @@ namespace Renci.SshNet f.Value)); } - // Call callback to report number of files read + // Call callback to report number of files read if (listCallback is not null) { - // Execute callback on different thread + // Execute callback on different thread ThreadAbstraction.ExecuteThread(() => listCallback(result.Count)); } @@ -2377,10 +2382,10 @@ namespace Renci.SshNet if (downloadCallback is not null) { - // copy offset to ensure it's not modified between now and execution of callback + // Copy offset to ensure it's not modified between now and execution of callback var downloadOffset = totalBytesRead; - // Execute callback on different thread + // Execute callback on different thread ThreadAbstraction.ExecuteThread(() => { downloadCallback(downloadOffset); }); } } @@ -2447,10 +2452,10 @@ namespace Renci.SshNet _ = Interlocked.Decrement(ref expectedResponses); _ = responseReceivedWaitHandle.Set(); - // Call callback to report number of bytes written + // Call callback to report number of bytes written if (uploadCallback is not null) { - // Execute callback on different thread + // Execute callback on different thread ThreadAbstraction.ExecuteThread(() => uploadCallback(writtenBytes)); } } @@ -2464,13 +2469,14 @@ namespace Renci.SshNet } else if (expectedResponses > 0) { - // Wait for expectedResponses to change + // Wait for expectedResponses to change _sftpSession.WaitOnHandle(responseReceivedWaitHandle, _operationTimeout); } } while (expectedResponses > 0 || bytesRead > 0); _sftpSession.RequestClose(handle); + responseReceivedWaitHandle.Dispose(); } /// @@ -2501,7 +2507,7 @@ namespace Renci.SshNet } /// - /// Releases unmanaged and - optionally - managed resources + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected override void Dispose(bool disposing) diff --git a/src/Renci.SshNet/Shell.cs b/src/Renci.SshNet/Shell.cs index 0f80c3ca..4a73e58f 100644 --- a/src/Renci.SshNet/Shell.cs +++ b/src/Renci.SshNet/Shell.cs @@ -24,9 +24,9 @@ namespace Renci.SshNet private readonly Stream _outputStream; private readonly Stream _extendedOutputStream; private readonly int _bufferSize; - private EventWaitHandle _dataReaderTaskCompleted; + private ManualResetEvent _dataReaderTaskCompleted; private IChannelSession _channel; - private EventWaitHandle _channelClosedWaitHandle; + private AutoResetEvent _channelClosedWaitHandle; private Stream _input; /// @@ -130,7 +130,7 @@ namespace Renci.SshNet var readTask = _input.ReadAsync(buffer, 0, buffer.Length); var readWaitHandle = ((IAsyncResult) readTask).AsyncWaitHandle; - if (WaitHandle.WaitAny(new[] {readWaitHandle, _channelClosedWaitHandle}) == 0) + if (WaitHandle.WaitAny(new[] { readWaitHandle, _channelClosedWaitHandle }) == 0) { var read = readTask.GetAwaiter().GetResult(); _channel.SendData(buffer, 0, read); diff --git a/src/Renci.SshNet/ShellStream.cs b/src/Renci.SshNet/ShellStream.cs index 80097baf..ce57072d 100644 --- a/src/Renci.SshNet/ShellStream.cs +++ b/src/Renci.SshNet/ShellStream.cs @@ -155,10 +155,14 @@ namespace Renci.SshNet /// Methods were called after the stream was closed. public override void Flush() { +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_channel is null, this); +#else if (_channel is null) { - throw new ObjectDisposedException("ShellStream"); + throw new ObjectDisposedException(GetType().FullName); } +#endif // NET7_0_OR_GREATER if (_outgoing.Count > 0) { @@ -199,36 +203,6 @@ namespace Renci.SshNet set { throw new NotSupportedException(); } } - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. - /// The zero-based byte offset in at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. - /// - /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. - /// - /// The sum of and is larger than the buffer length. - /// is . - /// or is negative. - /// An I/O error occurs. - /// The stream does not support reading. - /// Methods were called after the stream was closed. - public override int Read(byte[] buffer, int offset, int count) - { - var i = 0; - - lock (_incoming) - { - for (; i < count && _incoming.Count > 0; i++) - { - buffer[offset + i] = _incoming.Dequeue(); - } - } - - return i; - } - /// /// This method is not supported. /// @@ -257,31 +231,6 @@ namespace Renci.SshNet throw new NotSupportedException(); } - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// An array of bytes. This method copies bytes from to the current stream. - /// The zero-based byte offset in at which to begin copying bytes to the current stream. - /// The number of bytes to be written to the current stream. - /// The sum of and is greater than the buffer length. - /// is . - /// or is negative. - /// An I/O error occurs. - /// The stream does not support writing. - /// Methods were called after the stream was closed. - public override void Write(byte[] buffer, int offset, int count) - { - foreach (var b in buffer.Take(offset, count)) - { - if (_outgoing.Count == _bufferSize) - { - Flush(); - } - - _outgoing.Enqueue(b); - } - } - /// /// Expects the specified expression and performs action when one is found. /// @@ -351,148 +300,6 @@ namespace Renci.SshNet while (!expectedFound); } - /// - /// Begins the expect. - /// - /// The expect actions. - /// - /// An that references the asynchronous operation. - /// - public IAsyncResult BeginExpect(params ExpectAction[] expectActions) - { - return BeginExpect(TimeSpan.Zero, callback: null, state: null, expectActions); - } - - /// - /// Begins the expect. - /// - /// The callback. - /// The expect actions. - /// - /// An that references the asynchronous operation. - /// - public IAsyncResult BeginExpect(AsyncCallback callback, params ExpectAction[] expectActions) - { - return BeginExpect(TimeSpan.Zero, callback, state: null, expectActions); - } - - /// - /// Begins the expect. - /// - /// The callback. - /// The state. - /// The expect actions. - /// - /// An that references the asynchronous operation. - /// - public IAsyncResult BeginExpect(AsyncCallback callback, object state, params ExpectAction[] expectActions) - { - return BeginExpect(TimeSpan.Zero, callback, state, expectActions); - } - - /// - /// Begins the expect. - /// - /// The timeout. - /// The callback. - /// The state. - /// The expect actions. - /// - /// An that references the asynchronous operation. - /// - public IAsyncResult BeginExpect(TimeSpan timeout, AsyncCallback callback, object state, params ExpectAction[] expectActions) - { - var text = string.Empty; - - // Create new AsyncResult object - var asyncResult = new ExpectAsyncResult(callback, state); - - // Execute callback on different thread - ThreadAbstraction.ExecuteThread(() => - { - string expectActionResult = null; - try - { - do - { - lock (_incoming) - { - if (_incoming.Count > 0) - { - text = _encoding.GetString(_incoming.ToArray(), 0, _incoming.Count); - } - - if (text.Length > 0) - { - foreach (var expectAction in expectActions) - { - var match = expectAction.Expect.Match(text); - - if (match.Success) - { - var result = text.Substring(0, match.Index + match.Length); - - for (var i = 0; i < match.Index + match.Length && _incoming.Count > 0; i++) - { - // Remove processed items from the queue - _ = _incoming.Dequeue(); - } - - expectAction.Action(result); - callback?.Invoke(asyncResult); - expectActionResult = result; - } - } - } - } - - if (expectActionResult != null) - { - break; - } - - if (timeout.Ticks > 0) - { - if (!_dataReceived.WaitOne(timeout)) - { - callback?.Invoke(asyncResult); - break; - } - } - else - { - _ = _dataReceived.WaitOne(); - } - } - while (true); - - asyncResult.SetAsCompleted(expectActionResult, completedSynchronously: true); - } - catch (Exception exp) - { - asyncResult.SetAsCompleted(exp, completedSynchronously: true); - } - }); - - return asyncResult; - } - - /// - /// Ends the execute. - /// - /// The async result. - /// Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult. - public string EndExpect(IAsyncResult asyncResult) - { - if (asyncResult is not ExpectAsyncResult ar || ar.EndInvokeCalled) - { - throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult."); - } - - // Wait for operation to complete, then return result or throw exception - return ar.EndInvoke(); - } - /// /// Expects the expression specified by text. /// @@ -582,6 +389,153 @@ namespace Renci.SshNet return text; } + /// + /// Begins the expect. + /// + /// The expect actions. + /// + /// An that references the asynchronous operation. + /// + public IAsyncResult BeginExpect(params ExpectAction[] expectActions) + { + return BeginExpect(TimeSpan.Zero, callback: null, state: null, expectActions); + } + + /// + /// Begins the expect. + /// + /// The callback. + /// The expect actions. + /// + /// An that references the asynchronous operation. + /// + public IAsyncResult BeginExpect(AsyncCallback callback, params ExpectAction[] expectActions) + { + return BeginExpect(TimeSpan.Zero, callback, state: null, expectActions); + } + + /// + /// Begins the expect. + /// + /// The callback. + /// The state. + /// The expect actions. + /// + /// An that references the asynchronous operation. + /// + public IAsyncResult BeginExpect(AsyncCallback callback, object state, params ExpectAction[] expectActions) + { + return BeginExpect(TimeSpan.Zero, callback, state, expectActions); + } + + /// + /// Begins the expect. + /// + /// The timeout. + /// The callback. + /// The state. + /// The expect actions. + /// + /// An that references the asynchronous operation. + /// +#pragma warning disable CA1859 // Use concrete types when possible for improved performance + public IAsyncResult BeginExpect(TimeSpan timeout, AsyncCallback callback, object state, params ExpectAction[] expectActions) +#pragma warning restore CA1859 // Use concrete types when possible for improved performance + { + var text = string.Empty; + + // Create new AsyncResult object + var asyncResult = new ExpectAsyncResult(callback, state); + + // Execute callback on different thread + ThreadAbstraction.ExecuteThread(() => + { + string expectActionResult = null; + try + { + do + { + lock (_incoming) + { + if (_incoming.Count > 0) + { + text = _encoding.GetString(_incoming.ToArray(), 0, _incoming.Count); + } + + if (text.Length > 0) + { + foreach (var expectAction in expectActions) + { + var match = expectAction.Expect.Match(text); + + if (match.Success) + { + var result = text.Substring(0, match.Index + match.Length); + + for (var i = 0; i < match.Index + match.Length && _incoming.Count > 0; i++) + { + // Remove processed items from the queue + _ = _incoming.Dequeue(); + } + + expectAction.Action(result); + callback?.Invoke(asyncResult); + expectActionResult = result; + } + } + } + } + + if (expectActionResult != null) + { + break; + } + + if (timeout.Ticks > 0) + { + if (!_dataReceived.WaitOne(timeout)) + { + callback?.Invoke(asyncResult); + break; + } + } + else + { + _ = _dataReceived.WaitOne(); + } + } + while (true); + + asyncResult.SetAsCompleted(expectActionResult, completedSynchronously: true); + } + catch (Exception exp) + { + asyncResult.SetAsCompleted(exp, completedSynchronously: true); + } + }); + + return asyncResult; + } + + /// + /// Ends the execute. + /// + /// The async result. + /// + /// Text available in the shell that ends with expected text. + /// + /// Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult. + public string EndExpect(IAsyncResult asyncResult) + { + if (asyncResult is not ExpectAsyncResult ar || ar.EndInvokeCalled) + { + throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult."); + } + + // Wait for operation to complete, then return result or throw exception + return ar.EndInvoke(); + } + /// /// Reads the line from the shell. If line is not available it will block the execution and will wait for new line. /// @@ -667,6 +621,36 @@ namespace Renci.SshNet return text; } + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// + /// The sum of and is larger than the buffer length. + /// is . + /// or is negative. + /// An I/O error occurs. + /// The stream does not support reading. + /// Methods were called after the stream was closed. + public override int Read(byte[] buffer, int offset, int count) + { + var i = 0; + + lock (_incoming) + { + for (; i < count && _incoming.Count > 0; i++) + { + buffer[offset + i] = _incoming.Dequeue(); + } + } + + return i; + } + /// /// Writes the specified text to the shell. /// @@ -681,15 +665,44 @@ namespace Renci.SshNet return; } +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_channel is null, this); +#else if (_channel is null) { - throw new ObjectDisposedException("ShellStream"); + throw new ObjectDisposedException(GetType().FullName); } +#endif // NET7_0_OR_GREATER var data = _encoding.GetBytes(text); _channel.SendData(data); } + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies bytes from to the current stream. + /// The zero-based byte offset in at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + /// The sum of and is greater than the buffer length. + /// is . + /// or is negative. + /// An I/O error occurs. + /// The stream does not support writing. + /// Methods were called after the stream was closed. + public override void Write(byte[] buffer, int offset, int count) + { + foreach (var b in buffer.Take(offset, count)) + { + if (_outgoing.Count == _bufferSize) + { + Flush(); + } + + _outgoing.Enqueue(b); + } + } + /// /// Writes the line to the shell. /// diff --git a/src/Renci.SshNet/SshClient.cs b/src/Renci.SshNet/SshClient.cs index 171b7977..753ab8f9 100644 --- a/src/Renci.SshNet/SshClient.cs +++ b/src/Renci.SshNet/SshClient.cs @@ -27,7 +27,7 @@ namespace Renci.SshNet /// private bool _isDisposed; - private Stream _inputStream; + private MemoryStream _inputStream; /// /// Gets the list of forwarded ports. @@ -68,7 +68,9 @@ namespace Renci.SshNet /// is not within and . [SuppressMessage("Microsoft.Reliability", "C2A000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")] public SshClient(string host, int port, string username, string password) +#pragma warning disable CA2000 // Dispose objects before losing scope : this(new PasswordConnectionInfo(host, port, username, password), ownsConnectionInfo: true) +#pragma warning restore CA2000 // Dispose objects before losing scope { } @@ -359,12 +361,18 @@ namespace Renci.SshNet /// Client is not connected. public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes, int bufferSize) { - // TODO let shell dispose of input stream when we own the stream! + /* + * TODO Issue #1224: let shell dispose of input stream when we own the stream! + */ _inputStream = new MemoryStream(); - var writer = new StreamWriter(_inputStream, encoding); - writer.Write(input); - writer.Flush(); + + using (var writer = new StreamWriter(_inputStream, encoding, bufferSize: 1024, leaveOpen: true)) + { + writer.Write(input); + writer.Flush(); + } + _ = _inputStream.Seek(0, SeekOrigin.Begin); return CreateShell(_inputStream, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, bufferSize); diff --git a/src/Renci.SshNet/SshCommand.cs b/src/Renci.SshNet/SshCommand.cs index 9bcd3034..5b93354f 100644 --- a/src/Renci.SshNet/SshCommand.cs +++ b/src/Renci.SshNet/SshCommand.cs @@ -62,7 +62,9 @@ namespace Renci.SshNet /// /// /// +#pragma warning disable CA1859 // Use concrete types when possible for improved performance public Stream OutputStream { get; private set; } +#pragma warning restore CA1859 // Use concrete types when possible for improved performance /// /// Gets the extended output stream. @@ -70,7 +72,9 @@ namespace Renci.SshNet /// /// /// +#pragma warning disable CA1859 // Use concrete types when possible for improved performance public Stream ExtendedOutputStream { get; private set; } +#pragma warning restore CA1859 // Use concrete types when possible for improved performance /// /// Gets the command execution result. @@ -86,9 +90,14 @@ namespace Renci.SshNet if (OutputStream != null && OutputStream.Length > 0) { - // do not dispose the StreamReader, as it would also dispose the stream - var sr = new StreamReader(OutputStream, _encoding); - _ = _result.Append(sr.ReadToEnd()); + using (var sr = new StreamReader(OutputStream, + _encoding, + detectEncodingFromByteOrderMarks: true, + bufferSize: 1024, + leaveOpen: true)) + { + _ = _result.Append(sr.ReadToEnd()); + } } return _result.ToString(); @@ -111,9 +120,14 @@ namespace Renci.SshNet if (ExtendedOutputStream != null && ExtendedOutputStream.Length > 0) { - // do not dispose the StreamReader, as it would also dispose the stream - var sr = new StreamReader(ExtendedOutputStream, _encoding); - _ = _error.Append(sr.ReadToEnd()); + using (var sr = new StreamReader(ExtendedOutputStream, + _encoding, + detectEncodingFromByteOrderMarks: true, + bufferSize: 1024, + leaveOpen: true)) + { + _ = _error.Append(sr.ReadToEnd()); + } } return _error.ToString(); @@ -206,7 +220,9 @@ namespace Renci.SshNet /// CommandText property is empty. /// Client is not connected. /// Operation has timed out. +#pragma warning disable CA1859 // Use concrete types when possible for improved performance public IAsyncResult BeginExecute(AsyncCallback callback, object state) +#pragma warning restore CA1859 // Use concrete types when possible for improved performance { // Prevent from executing BeginExecute before calling EndExecute if (_asyncResult != null && !_asyncResult.EndCalled) @@ -321,10 +337,24 @@ namespace Renci.SshNet } } + /// + /// Cancels command execution in asynchronous scenarios. + /// + public void CancelAsync() + { + if (_channel is not null && _channel.IsOpen && _asyncResult is not null) + { + // TODO: check with Oleg if we shouldn't dispose the channel and uninitialize it ? + _channel.Dispose(); + } + } + /// /// Executes command specified by property. /// - /// Command execution result + /// + /// Command execution result. + /// /// /// /// @@ -337,18 +367,6 @@ namespace Renci.SshNet return EndExecute(BeginExecute(callback: null, state: null)); } - /// - /// Cancels command execution in asynchronous scenarios. - /// - public void CancelAsync() - { - if (_channel is not null && _channel.IsOpen && _asyncResult is not null) - { - // TODO: check with Oleg if we shouldn't dispose the channel and uninitialize it ? - _channel.Dispose(); - } - } - /// /// Executes the specified command text. /// @@ -493,7 +511,6 @@ namespace Renci.SshNet throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Command '{0}' has timed out.", CommandText)); default: throw new SshException($"Unexpected element '{signaledElement.ToString(CultureInfo.InvariantCulture)}' signaled."); - } } diff --git a/src/Renci.SshNet/SshMessageFactory.cs b/src/Renci.SshNet/SshMessageFactory.cs index 5941c120..efa86125 100644 --- a/src/Renci.SshNet/SshMessageFactory.cs +++ b/src/Renci.SshNet/SshMessageFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; + using Renci.SshNet.Common; using Renci.SshNet.Messages; using Renci.SshNet.Messages.Authentication; @@ -13,9 +14,44 @@ namespace Renci.SshNet { private readonly MessageMetadata[] _enabledMessagesByNumber; private readonly bool[] _activatedMessagesById; + private readonly object _lock = new object(); - internal static readonly MessageMetadata[] AllMessages; - private static readonly Dictionary MessagesByName; + private static readonly MessageMetadata[] AllMessages = new MessageMetadata[] + { + new MessageMetadata(0, "SSH_MSG_KEXINIT", 20), + new MessageMetadata(1, "SSH_MSG_NEWKEYS", 21), + new MessageMetadata(2, "SSH_MSG_REQUEST_FAILURE", 82), + new MessageMetadata(3, "SSH_MSG_CHANNEL_OPEN_FAILURE", 92), + new MessageMetadata(4, "SSH_MSG_CHANNEL_FAILURE", 100), + new MessageMetadata(5, "SSH_MSG_CHANNEL_EXTENDED_DATA", 95), + new MessageMetadata(6, "SSH_MSG_CHANNEL_DATA", 94), + new MessageMetadata(7, "SSH_MSG_CHANNEL_REQUEST", 98), + new MessageMetadata(8, "SSH_MSG_USERAUTH_BANNER", 53), + new MessageMetadata(9, "SSH_MSG_USERAUTH_INFO_RESPONSE", 61), + new MessageMetadata(10, "SSH_MSG_USERAUTH_FAILURE", 51), + new MessageMetadata(11, "SSH_MSG_DEBUG", 4), + new MessageMetadata(12, "SSH_MSG_GLOBAL_REQUEST", 80), + new MessageMetadata(13, "SSH_MSG_CHANNEL_OPEN", 90), + new MessageMetadata(14, "SSH_MSG_CHANNEL_OPEN_CONFIRMATION", 91), + new MessageMetadata(15, "SSH_MSG_USERAUTH_INFO_REQUEST", 60), + new MessageMetadata(16, "SSH_MSG_UNIMPLEMENTED", 3), + new MessageMetadata(17, "SSH_MSG_REQUEST_SUCCESS", 81), + new MessageMetadata(18, "SSH_MSG_CHANNEL_SUCCESS", 99), + new MessageMetadata(19, "SSH_MSG_USERAUTH_PASSWD_CHANGEREQ", 60), + new MessageMetadata(20, "SSH_MSG_DISCONNECT", 1), + new MessageMetadata(21, "SSH_MSG_USERAUTH_SUCCESS", 52), + new MessageMetadata(22, "SSH_MSG_USERAUTH_PK_OK", 60), + new MessageMetadata(23, "SSH_MSG_IGNORE", 2), + new MessageMetadata(24, "SSH_MSG_CHANNEL_WINDOW_ADJUST", 93), + new MessageMetadata(25, "SSH_MSG_CHANNEL_EOF", 96), + new MessageMetadata(26, "SSH_MSG_CHANNEL_CLOSE", 97), + new MessageMetadata(27, "SSH_MSG_SERVICE_ACCEPT", 6), + new MessageMetadata(28, "SSH_MSG_KEX_DH_GEX_GROUP", 31), + new MessageMetadata(29, "SSH_MSG_KEXDH_REPLY", 31), + new MessageMetadata(30, "SSH_MSG_KEX_DH_GEX_REPLY", 33), + new MessageMetadata(31, "SSH_MSG_KEX_ECDH_REPLY", 31) + }; + private static readonly Dictionary MessagesByName = CreateMessagesByNameMapping(); /// /// Defines the highest message number that is currently supported. @@ -27,52 +63,6 @@ namespace Renci.SshNet /// internal const int TotalMessageCount = 32; - static SshMessageFactory() - { - AllMessages = new MessageMetadata[] - { - new MessageMetadata(0, "SSH_MSG_KEXINIT", 20), - new MessageMetadata(1, "SSH_MSG_NEWKEYS", 21), - new MessageMetadata(2, "SSH_MSG_REQUEST_FAILURE", 82), - new MessageMetadata(3, "SSH_MSG_CHANNEL_OPEN_FAILURE", 92), - new MessageMetadata(4, "SSH_MSG_CHANNEL_FAILURE", 100), - new MessageMetadata(5, "SSH_MSG_CHANNEL_EXTENDED_DATA", 95), - new MessageMetadata(6, "SSH_MSG_CHANNEL_DATA", 94), - new MessageMetadata(7, "SSH_MSG_CHANNEL_REQUEST", 98), - new MessageMetadata(8, "SSH_MSG_USERAUTH_BANNER", 53), - new MessageMetadata(9, "SSH_MSG_USERAUTH_INFO_RESPONSE", 61), - new MessageMetadata(10, "SSH_MSG_USERAUTH_FAILURE", 51), - new MessageMetadata(11, "SSH_MSG_DEBUG", 4), - new MessageMetadata(12, "SSH_MSG_GLOBAL_REQUEST", 80), - new MessageMetadata(13, "SSH_MSG_CHANNEL_OPEN", 90), - new MessageMetadata(14, "SSH_MSG_CHANNEL_OPEN_CONFIRMATION", 91), - new MessageMetadata(15, "SSH_MSG_USERAUTH_INFO_REQUEST", 60), - new MessageMetadata(16, "SSH_MSG_UNIMPLEMENTED", 3), - new MessageMetadata(17, "SSH_MSG_REQUEST_SUCCESS", 81), - new MessageMetadata(18, "SSH_MSG_CHANNEL_SUCCESS", 99), - new MessageMetadata(19, "SSH_MSG_USERAUTH_PASSWD_CHANGEREQ", 60), - new MessageMetadata(20, "SSH_MSG_DISCONNECT", 1), - new MessageMetadata(21, "SSH_MSG_USERAUTH_SUCCESS", 52), - new MessageMetadata(22, "SSH_MSG_USERAUTH_PK_OK", 60), - new MessageMetadata(23, "SSH_MSG_IGNORE", 2), - new MessageMetadata(24, "SSH_MSG_CHANNEL_WINDOW_ADJUST", 93), - new MessageMetadata(25, "SSH_MSG_CHANNEL_EOF", 96), - new MessageMetadata(26, "SSH_MSG_CHANNEL_CLOSE", 97), - new MessageMetadata(27, "SSH_MSG_SERVICE_ACCEPT", 6), - new MessageMetadata(28, "SSH_MSG_KEX_DH_GEX_GROUP", 31), - new MessageMetadata(29, "SSH_MSG_KEXDH_REPLY", 31), - new MessageMetadata(30, "SSH_MSG_KEX_DH_GEX_REPLY", 33), - new MessageMetadata(31, "SSH_MSG_KEX_ECDH_REPLY", 31) - }; - - MessagesByName = new Dictionary(AllMessages.Length); - for (var i = 0; i < AllMessages.Length; i++) - { - var messageMetadata = AllMessages[i]; - MessagesByName.Add(messageMetadata.Name, messageMetadata); - } - } - /// /// Initializes a new instance of the class. /// @@ -169,7 +159,7 @@ namespace Renci.SshNet throw new ArgumentNullException(nameof(messageName)); } - lock (this) + lock (_lock) { if (!MessagesByName.TryGetValue(messageName, out var messageMetadata)) { @@ -196,7 +186,7 @@ namespace Renci.SshNet throw new ArgumentNullException(nameof(messageName)); } - lock (this) + lock (_lock) { if (!MessagesByName.TryGetValue(messageName, out var messageMetadata)) { @@ -216,6 +206,19 @@ namespace Renci.SshNet } } + private static Dictionary CreateMessagesByNameMapping() + { + var messagesByName = new Dictionary(AllMessages.Length); + + for (var i = 0; i < AllMessages.Length; i++) + { + var messageMetadata = AllMessages[i]; + messagesByName.Add(messageMetadata.Name, messageMetadata); + } + + return messagesByName; + } + private static SshException CreateMessageTypeNotSupportedException(byte messageNumber) { throw new SshException(string.Format(CultureInfo.InvariantCulture, "Message type {0} is not supported.", messageNumber)); @@ -229,11 +232,13 @@ namespace Renci.SshNet private static SshException CreateMessageTypeAlreadyEnabledForOtherMessageException(byte messageNumber, string messageName, string currentEnabledForMessageName) { throw new SshException(string.Format(CultureInfo.InvariantCulture, - "Cannot enable message '{0}'. Message type {1} is already enabled for '{2}'.", - messageName, messageNumber, currentEnabledForMessageName)); + "Cannot enable message '{0}'. Message type {1} is already enabled for '{2}'.", + messageName, + messageNumber, + currentEnabledForMessageName)); } - internal abstract class MessageMetadata + private abstract class MessageMetadata { protected MessageMetadata(byte id, string name, byte number) { @@ -242,16 +247,16 @@ namespace Renci.SshNet Number = number; } - public readonly byte Id; + public byte Id { get; } - public readonly string Name; + public string Name { get; } - public readonly byte Number; + public byte Number { get; } public abstract Message Create(); } - internal sealed class MessageMetadata : MessageMetadata + private sealed class MessageMetadata : MessageMetadata where T : Message, new() { public MessageMetadata(byte id, string name, byte number) diff --git a/src/Renci.SshNet/SubsystemSession.cs b/src/Renci.SshNet/SubsystemSession.cs index ab6182bb..70385d90 100644 --- a/src/Renci.SshNet/SubsystemSession.cs +++ b/src/Renci.SshNet/SubsystemSession.cs @@ -307,12 +307,12 @@ namespace Renci.SshNet /// Blocks the current thread until the specified gets signaled, using a /// 32-bit signed integer to specify the time interval in milliseconds. /// - /// The first handle to wait for. - /// The second handle to wait for. + /// The first handle to wait for. + /// The second handle to wait for. /// To number of milliseconds to wait for a to get signaled, or -1 to wait indefinitely. /// - /// 0 if received a signal within the specified timeout, and 1 - /// if received a signal within the specified timeout. + /// 0 if received a signal within the specified timeout, and 1 + /// if received a signal within the specified timeout. /// /// The connection was closed by the server. /// The channel was closed. @@ -324,19 +324,19 @@ namespace Renci.SshNet /// or session event. /// /// - /// When both and are signaled during the call, + /// When both and are signaled during the call, /// then 0 is returned. /// /// - public int WaitAny(WaitHandle waitHandle1, WaitHandle waitHandle2, int millisecondsTimeout) + public int WaitAny(WaitHandle waitHandleA, WaitHandle waitHandleB, int millisecondsTimeout) { var waitHandles = new[] { _errorOccuredWaitHandle, _sessionDisconnectedWaitHandle, _channelClosedWaitHandle, - waitHandle1, - waitHandle2 + waitHandleA, + waitHandleB }; var result = WaitHandle.WaitAny(waitHandles, millisecondsTimeout); @@ -547,10 +547,14 @@ namespace Renci.SshNet private void EnsureNotDisposed() { +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_isDisposed, this); +#else if (_isDisposed) { throw new ObjectDisposedException(GetType().FullName); } +#endif // NET7_0_OR_GREATER } } } diff --git a/test/.editorconfig b/test/.editorconfig index ff0eebcf..60515fd1 100644 --- a/test/.editorconfig +++ b/test/.editorconfig @@ -1,6 +1,10 @@ [*.cs] -# Sonar rules +#### Sonar rules #### + +# S1215: ""GC.Collect" should not be called +# https://rules.sonarsource.com/csharp/RSPEC-1215 +dotnet_diagnostic.S1215.severity = none # S1854: Unused assignments should be removed # https://rules.sonarsource.com/csharp/RSPEC-1854 @@ -18,6 +22,12 @@ # For unit tests, we do not care about this diagnostic. dotnet_diagnostic.S1854.severity = none +#### Meziantou.Analyzer rules #### + +# MA0089: Optimize string method usage +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0089.md +dotnet_diagnostic.MA0089.severity = suggestion + #### StyleCop rules #### # SA1202: Elements must be ordered by access @@ -95,6 +105,18 @@ dotnet_diagnostic.CA1001.severity = none # We frequently use underscores in test classes and test methods. dotnet_diagnostic.CA1707.severity = none +# CA1824: Mark assemblies with NeutralResourcesLanguageAttribute +# https://learn.microsoft.com/en-US/dotnet/fundamentals/code-analysis/quality-rules/ca1824 +# +# We do not care (much) about performance for tests. +dotnet_diagnostic.CA1824.severity = none + +# CA1835: Prefer the memory-based overloads of ReadAsync/WriteAsync methods in stream-based classes +# https://learn.microsoft.com/en-US/dotnet/fundamentals/code-analysis/quality-rules/ca1835 +# +# We do not care about this for unit tests. +dotnet_diagnostic.CA1835.severity = none + # CA1711: Identifiers should not have incorrect suffix # # We frequently define test classes and test method with a suffix that refers to a type. @@ -105,6 +127,11 @@ dotnet_diagnostic.CA1711.severity = none # We do not care about this for unit tests. dotnet_diagnostic.CA1720.severity = none +# CA5351: Do not use broken cryptographic algorithms +# +# We do not care about this for unit tests. +dotnet_diagnostic.CA5351.severity = none + # CA5394: Do not use insecure randomness # # We do not care about this for unit tests. diff --git a/test/Renci.SshNet.Benchmarks/.editorconfig b/test/Renci.SshNet.Benchmarks/.editorconfig new file mode 100644 index 00000000..e903a76d --- /dev/null +++ b/test/Renci.SshNet.Benchmarks/.editorconfig @@ -0,0 +1,72 @@ +[*.cs] + +#### Sonar rules #### + +# S125: Sections of code should not be commented out +https://rules.sonarsource.com/csharp/RSPEC-125/ +dotnet_diagnostic.S125.severity = suggestion + +# S1118: Utility classes should not have public constructors +# https://rules.sonarsource.com/csharp/RSPEC-1118/ +dotnet_diagnostic.S1118.severity = suggestion + +# S1450: Private fields only used as local variables in methods should become local variables +# https://rules.sonarsource.com/csharp/RSPEC-1450/ +dotnet_diagnostic.S1450.severity = suggestion + +# S4144: Methods should not have identical implementations +# https://rules.sonarsource.com/csharp/RSPEC-4144/ +dotnet_diagnostic.S4144.severity = suggestion + +#### SYSLIB diagnostics #### + + +#### StyleCop Analyzers rules #### + +# SA1028: Code must not contain trailing whitespace +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md +dotnet_diagnostic.SA1028.severity = suggestion + +# SA1414: Tuple types in signatures should have element names +https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1414.md +dotnet_diagnostic.SA1414.severity = suggestion + +# SA1400: Access modifier must be declared +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1400.md +dotnet_diagnostic.SA1400.severity = suggestion + +# SA1401: Fields must be private +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md +dotnet_diagnostic.SA1401.severity = suggestion + +# SA1411: Attribute constructor must not use unnecessary parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1411.md +dotnet_diagnostic.SA1411.severity = suggestion + +# SA1505: Opening braces must not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1505.md +dotnet_diagnostic.SA1505.severity = suggestion + +# SA1512: Single line comments must not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1512.md +dotnet_diagnostic.SA1512.severity = suggestion + +# SA1513: Closing brace must be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md +dotnet_diagnostic.SA1513.severity = suggestion + +#### Meziantou.Analyzer rules #### + +# MA0003: Add parameter name to improve readability +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0003.md +dotnet_diagnostic.MA0003.severity = suggestion + +# MA0053: Make class sealed +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0053.md +dotnet_diagnostic.MA0053.severity = suggestion + +#### .NET Compiler Platform analysers rules #### + +# CA2000: Dispose objects before losing scope +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2000 +dotnet_diagnostic.CA2000.severity = suggestion diff --git a/test/Renci.SshNet.IntegrationTests/.editorconfig b/test/Renci.SshNet.IntegrationTests/.editorconfig index b94e2911..dfeca510 100644 --- a/test/Renci.SshNet.IntegrationTests/.editorconfig +++ b/test/Renci.SshNet.IntegrationTests/.editorconfig @@ -1,5 +1,59 @@ [*.cs] +#### Sonar rules #### + +# S108: Nested blocks of code should not be left empty +# https://rules.sonarsource.com/csharp/RSPEC-108/ +dotnet_diagnostic.S108.severity = none + +# S125: Sections of code should not be commented out +# https://rules.sonarsource.com/csharp/RSPEC-125/ +dotnet_diagnostic.S125.severity = none + +# S1118: Utility classes should not have public constructors +# https://rules.sonarsource.com/csharp/RSPEC-1118/ +dotnet_diagnostic.S1118.severity = none + +# S1155: "Any()" should be used to test for emptiness +# https://rules.sonarsource.com/csharp/RSPEC-1155/ +dotnet_diagnostic.S1155.severity = none + +# S1607: Tests should not be ignored +# https://rules.sonarsource.com/csharp/RSPEC-1607/ +dotnet_diagnostic.S1607.severity = none + +# S2925: "Thread.Sleep" should not be used in tests +# https://rules.sonarsource.com/csharp/RSPEC-2925/ +dotnet_diagnostic.S2925.severity = none + +# 53241: Methods should not return values that are never used +# https://rules.sonarsource.com/csharp/RSPEC-3241/ +dotnet_diagnostic.S3241.severity = none + +# S3415: Assertion arguments should be passed in the correct order +# https://rules.sonarsource.com/csharp/RSPEC-3415/ +dotnet_diagnostic.S3415.severity = none + +# S3881: "IDisposable" should be implemented correctly +# https://rules.sonarsource.com/csharp/RSPEC-3881/ +dotnet_diagnostic.S3881.severity = none + +# S3966: Objects should not be disposed more than once +# https://rules.sonarsource.com/csharp/RSPEC-3966/ +dotnet_diagnostic.S3966.severity = none + +# S4144: Methods should not have identical implementations +# https://rules.sonarsource.com/csharp/RSPEC-4144/ +dotnet_diagnostic.S4144.severity = none + +# S6602: "Find" method should be used instead of the "FirstOrDefault" extension +# https://rules.sonarsource.com/csharp/RSPEC-6602/ +dotnet_diagnostic.S6602.severity = none + +# S6610: "StartsWith" and "EndsWith" overloads that take a "char" should be used instead of the ones that take a "string" +# https://rules.sonarsource.com/csharp/RSPEC-6610/ +dotnet_diagnostic.S6610.severity = none + #### SYSLIB diagnostics #### # SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time @@ -9,8 +63,266 @@ dotnet_diagnostic.SYSLIB1045.severity = none ### StyleCop Analyzers rules ### +# SA1000: Keywords must be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1000.md +dotnet_diagnostic.SA1000.severity = suggestion + +# SA1004: Documentation lines must begin with single space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1004.md +dotnet_diagnostic.SA1004.severity = suggestion + +# SA1005: Single line comments must begin with single space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1005.md +dotnet_diagnostic.SA1005.severity = suggestion + +# SA1012: Opening braces must be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1012.md +dotnet_diagnostic.SA1012.severity = suggestion + +# SA1025: Code must not contain multiple whitespace in a row +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1025.md +dotnet_diagnostic.SA1025.severity = suggestion + +# SA1028: Code must not contain trailing whitespace +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md +dotnet_diagnostic.SA1028.severity = suggestion + +# SA1111: Closing parenthesis must be on line of last parameter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1111.md +dotnet_diagnostic.SA1111.severity = suggestion + +# SA1117: Parameters must be on same line or separate lines +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1117.md +dotnet_diagnostic.SA1117.severity = suggestion + +# SA1119: Statement must not use unnecessary parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1119.md +dotnet_diagnostic.SA1119.severity = suggestion + +# SA1122: Use String.Empty for empty strings +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1122.md +dotnet_diagnostic.SA1122.severity = suggestion + +# SA1123:Do not place regions within elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1123.md +dotnet_diagnostic.SA1123.severity = suggestion + +# SA1130: Use lambda syntax +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1130.md +dotnet_diagnostic.SA1130.severity = suggestion + +# SA1133: Do not combine attributes +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1133.md +dotnet_diagnostic.SA1133.severity = suggestion + +# SA1203: Constants must appear before fields +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1203.md +dotnet_diagnostic.SA1203.severity = suggestion + +# SA1204: Static elements must appear before instance elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1204.md +dotnet_diagnostic.SA1204.severity = suggestion + +# SA1400: Access modifier must be declared +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1400.md +dotnet_diagnostic.SA1400.severity = suggestion + +# SA1401: Fields must be private +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md +dotnet_diagnostic.SA1401.severity = suggestion + +# SA1411: Attribute constructor must not use unnecessary parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1411.md +dotnet_diagnostic.SA1411.severity = suggestion + +# SA1505: Opening braces must not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1505.md +dotnet_diagnostic.SA1505.severity = suggestion + +# SA1507: Code must not contain multiple blank lines in a row +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1507.md +dotnet_diagnostic.SA1507.severity = suggestion + +# SA1508: Closing braces must not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1508.md +dotnet_diagnostic.SA1508.severity = suggestion + +# SA1510: Chained statement blocks must not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1510.md +dotnet_diagnostic.SA1510.severity = suggestion + +# SA1512: Single line comments must not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1512.md +dotnet_diagnostic.SA1512.severity = suggestion + +# SA1513: Closing brace must be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md +dotnet_diagnostic.SA1513.severity = suggestion + +# SA1514: Element documentation header must be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1514.md +dotnet_diagnostic.SA1514.severity = suggestion + +# SA1515: Single line comment must be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1515.md +dotnet_diagnostic.SA1515.severity = suggestion + +# SA1517: Code must not contain blank lines at start of file +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1517.md +dotnet_diagnostic.SA1517.severity = suggestion + +# SA1518: Use line endings correctly at end of file +https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1518.md +dotnet_diagnostic.SA1518.severity = suggestion + +# SA1626: Single line comments must not use documentation style slashes +https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1626.md +dotnet_diagnostic.SA1626.severity = suggestion + +#### Meziantou.Analyzer rules #### + +# MA0001: StringComparison is missing +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0001.md +dotnet_diagnostic.MA0001.severity = suggestion + +# MA0003: Add parameter name to improve readability +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0003.md +dotnet_diagnostic.MA0003.severity = suggestion + +# MA0004: Use Task.ConfigureAwait(false) +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0004.md +dotnet_diagnostic.MA0004.severity = suggestion + +# MA0011: IFormatProvider is missing +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0011.md +dotnet_diagnostic.MA0011.severity = suggestion + +# MA0042: Do not use blocking calls in an async method +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0042.md +dotnet_diagnostic.MA0042.severity = suggestion + +# MA0043: Use nameof operator in ArgumentException +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0043.md +dotnet_diagnostic.MA0043.severity = suggestion + +# MA0046: Use EventHandler to declare events +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0046.md +dotnet_diagnostic.MA0046.severity = suggestion + +# MA0053: Make class sealed +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0053.md +dotnet_diagnostic.MA0053.severity = suggestion + +# MA0060: The value returned by Stream.Read/Stream.ReadAsync is not used +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0060.md +dotnet_diagnostic.MA0060.severity = suggestion + +# MA0074: Avoid implicit culture-sensitive methods +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0074.md +dotnet_diagnostic.MA0074.severity = suggestion + +# MA0075: Do not use implicit culture-sensitive ToString +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0075.md +dotnet_diagnostic.MA0075.severity = suggestion + +# MA0076: Do not use implicit culture-sensitive ToString in interpolated strings +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0076.md +dotnet_diagnostic.MA0076.severity = suggestion + +# MA0099 - Use Explicit enum value instead of 0 +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0099.md +dotnet_diagnostic.MA0099.severity = suggestion + +# MA0101: String contains an implicit end of line character +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0101.md +dotnet_diagnostic.MA0101.severity = suggestion + +# MA0110: Use the Regex source generator +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0110.md +dotnet_diagnostic.MA0110.severity = suggestion + #### .NET Compiler Platform analysers rules #### +# CA1031: Do not catch general exception types +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1031 +dotnet_diagnostic.CA1031.severity = suggestion + +# CA1052: Static holder types should be Static or NotInheritable +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1052 +dotnet_diagnostic.CA1052.severity = suggestion + +# CA1062: Validate arguments of public methods +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1062 +dotnet_diagnostic.CA1062.severity = suggestion + +# CA1063: Implement IDisposable correctly +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1063 +dotnet_diagnostic.CA1063.severity = suggestion + +# CA1307: Specify StringComparison for clarity +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1307 +dotnet_diagnostic.CA1307.severity = suggestion + +# CA1310: Specify StringComparison for correctness +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1310 +dotnet_diagnostic.CA1310.severity = suggestion + +# CA1507: Use nameof in place of string +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1507 +dotnet_diagnostic.CA1507.severity = suggestion + +# CA1827: Do not use Count()/LongCount() when Any() can be used +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1827 +dotnet_diagnostic.CA1827.severity = suggestion + +# CA1812: Avoid uninstantiated internal classes +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1812 +dotnet_diagnostic.CA1812.severity = suggestion + +# CA1816: Call GC.SuppressFinalize correctly +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1816 +dotnet_diagnostic.CA1816.severity = suggestion + +# CA1822: Mark members as static +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1822 +dotnet_diagnostic.CA1822.severity = suggestion + +# CA1840: Use Environment.CurrentManagedThreadId instead of Thread.CurrentThread.ManagedThreadId +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1840 +dotnet_diagnostic.CA1840.severity = suggestion + +# CA1851: Possible multiple enumerations of IEnumerable collection +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1851 +dotnet_diagnostic.CA1851.severity = suggestion + +# CA1859: Use concrete types when possible for improved performance +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1859 +dotnet_diagnostic.CA1859.severity = suggestion + +# CA2000: Dispose objects before losing scope +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2000 +dotnet_diagnostic.CA2000.severity = suggestion + +# CA2007: Do not directly await a Task +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2007 +dotnet_diagnostic.CA2007.severity = suggestion + +# CA2008: Do not create tasks without passing a TaskScheduler +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2008 +dotnet_diagnostic.CA2008.severity = suggestion + +# CA2201: Do not raise reserved exception types +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2201 +dotnet_diagnostic.CA2201.severity = suggestion + +# CA2213: Disposable fields should be disposed +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2213 +dotnet_diagnostic.CA2213.severity = suggestion + +# CA2234: Pass System.Uri objects instead of strings +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2234 +dotnet_diagnostic.CA2234.severity = suggestion + # IDE0007: Use var instead of explicit type # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0007 dotnet_diagnostic.IDE0007.severity = suggestion diff --git a/test/Renci.SshNet.IntegrationTests/HostConfig.cs b/test/Renci.SshNet.IntegrationTests/HostConfig.cs index 817bd702..d501dd38 100644 --- a/test/Renci.SshNet.IntegrationTests/HostConfig.cs +++ b/test/Renci.SshNet.IntegrationTests/HostConfig.cs @@ -98,18 +98,4 @@ namespace Renci.SshNet.IntegrationTests } } } - - public class HostEntry - { - public HostEntry(IPAddress ipAddress, string hostName) - { - IPAddress = ipAddress; - HostName = hostName; - Aliases = new List(); - } - - public IPAddress IPAddress { get; private set; } - public string HostName { get; set; } - public List Aliases { get; } - } } diff --git a/test/Renci.SshNet.IntegrationTests/HostEntry.cs b/test/Renci.SshNet.IntegrationTests/HostEntry.cs new file mode 100644 index 00000000..9127e820 --- /dev/null +++ b/test/Renci.SshNet.IntegrationTests/HostEntry.cs @@ -0,0 +1,18 @@ +using System.Net; + +namespace Renci.SshNet.IntegrationTests +{ + public sealed class HostEntry + { + public HostEntry(IPAddress ipAddress, string hostName) + { + IPAddress = ipAddress; + HostName = hostName; + Aliases = new List(); + } + + public IPAddress IPAddress { get; private set; } + public string HostName { get; set; } + public List Aliases { get; } + } +} diff --git a/test/Renci.SshNet.IntegrationTests/RemoteSshd.cs b/test/Renci.SshNet.IntegrationTests/RemoteSshd.cs index b9a32e67..14614b40 100644 --- a/test/Renci.SshNet.IntegrationTests/RemoteSshd.cs +++ b/test/Renci.SshNet.IntegrationTests/RemoteSshd.cs @@ -1,6 +1,4 @@ -using Renci.SshNet.TestTools.OpenSSH; - -namespace Renci.SshNet.IntegrationTests +namespace Renci.SshNet.IntegrationTests { internal class RemoteSshd { @@ -42,217 +40,4 @@ namespace Renci.SshNet.IntegrationTests return this; } } - - internal class RemoteSshdConfig - { - private const string SshdConfigFilePath = "/etc/ssh/sshd_config"; - private static readonly Encoding Utf8NoBom = new UTF8Encoding(false, true); - - private readonly RemoteSshd _remoteSshd; - private readonly IConnectionInfoFactory _connectionInfoFactory; - private readonly SshdConfig _config; - - public RemoteSshdConfig(RemoteSshd remoteSshd, IConnectionInfoFactory connectionInfoFactory) - { - _remoteSshd = remoteSshd; - _connectionInfoFactory = connectionInfoFactory; - - using (var client = new ScpClient(_connectionInfoFactory.Create())) - { - client.Connect(); - - using (var memoryStream = new MemoryStream()) - { - client.Download(SshdConfigFilePath, memoryStream); - - memoryStream.Position = 0; - _config = SshdConfig.LoadFrom(memoryStream, Encoding.UTF8); - } - } - } - - /// - /// Specifies whether challenge-response authentication is allowed. - /// - /// to allow challenge-response authentication. - /// - /// The current instance. - /// - public RemoteSshdConfig WithChallengeResponseAuthentication(bool? value) - { - _config.ChallengeResponseAuthentication = value; - return this; - } - - /// - /// Specifies whether to allow keyboard-interactive authentication. - /// - /// to allow keyboard-interactive authentication. - /// - /// The current instance. - /// - public RemoteSshdConfig WithKeyboardInteractiveAuthentication(bool value) - { - _config.KeyboardInteractiveAuthentication = value; - return this; - } - - /// - /// Specifies whether sshd should print /etc/motd when a user logs in interactively. - /// - /// if sshd should print /etc/motd when a user logs in interactively. - /// - /// The current instance. - /// - public RemoteSshdConfig PrintMotd(bool? value = true) - { - _config.PrintMotd = value; - return this; - } - - /// - /// Specifies whether TCP forwarding is permitted. - /// - /// to allow TCP forwarding. - /// - /// The current instance. - /// - public RemoteSshdConfig AllowTcpForwarding(bool? value = true) - { - _config.AllowTcpForwarding = value; - return this; - } - - public RemoteSshdConfig WithAuthenticationMethods(string user, string authenticationMethods) - { - var sshNetMatch = _config.Matches.FirstOrDefault(m => m.Users.Contains(user)); - if (sshNetMatch == null) - { - sshNetMatch = new Match(new[] { user }, new string[0]); - _config.Matches.Add(sshNetMatch); - } - - sshNetMatch.AuthenticationMethods = authenticationMethods; - - return this; - } - - public RemoteSshdConfig ClearCiphers() - { - _config.Ciphers.Clear(); - return this; - } - - public RemoteSshdConfig AddCipher(Cipher cipher) - { - _config.Ciphers.Add(cipher); - return this; - } - - public RemoteSshdConfig ClearKeyExchangeAlgorithms() - { - _config.KeyExchangeAlgorithms.Clear(); - return this; - } - - public RemoteSshdConfig AddKeyExchangeAlgorithm(KeyExchangeAlgorithm keyExchangeAlgorithm) - { - _config.KeyExchangeAlgorithms.Add(keyExchangeAlgorithm); - return this; - } - - public RemoteSshdConfig ClearPublicKeyAcceptedAlgorithms() - { - _config.PublicKeyAcceptedAlgorithms.Clear(); - return this; - } - - public RemoteSshdConfig AddPublicKeyAcceptedAlgorithm(PublicKeyAlgorithm publicKeyAlgorithm) - { - _config.PublicKeyAcceptedAlgorithms.Add(publicKeyAlgorithm); - return this; - } - - public RemoteSshdConfig ClearMessageAuthenticationCodeAlgorithms() - { - _config.MessageAuthenticationCodeAlgorithms.Clear(); - return this; - } - - public RemoteSshdConfig AddMessageAuthenticationCodeAlgorithm(MessageAuthenticationCodeAlgorithm messageAuthenticationCodeAlgorithm) - { - _config.MessageAuthenticationCodeAlgorithms.Add(messageAuthenticationCodeAlgorithm); - return this; - } - - public RemoteSshdConfig ClearHostKeyAlgorithms() - { - _config.HostKeyAlgorithms.Clear(); - return this; - } - - public RemoteSshdConfig AddHostKeyAlgorithm(HostKeyAlgorithm hostKeyAlgorithm) - { - _config.HostKeyAlgorithms.Add(hostKeyAlgorithm); - return this; - } - - public RemoteSshdConfig ClearSubsystems() - { - _config.Subsystems.Clear(); - return this; - } - - public RemoteSshdConfig AddSubsystem(Subsystem subsystem) - { - _config.Subsystems.Add(subsystem); - return this; - } - - public RemoteSshdConfig WithLogLevel(LogLevel logLevel) - { - _config.LogLevel = logLevel; - return this; - } - - public RemoteSshdConfig WithUsePAM(bool usePAM) - { - _config.UsePAM = usePAM; - return this; - } - - public RemoteSshdConfig ClearHostKeyFiles() - { - _config.HostKeyFiles.Clear(); - return this; - } - - public RemoteSshdConfig AddHostKeyFile(string hostKeyFile) - { - _config.HostKeyFiles.Add(hostKeyFile); - return this; - } - - public RemoteSshd Update() - { - using (var client = new ScpClient(_connectionInfoFactory.Create())) - { - client.Connect(); - - using (var memoryStream = new MemoryStream()) - using (var sw = new StreamWriter(memoryStream, Utf8NoBom)) - { - sw.NewLine = "\n"; - _config.SaveTo(sw); - sw.Flush(); - - memoryStream.Position = 0; - - client.Upload(memoryStream, SshdConfigFilePath); - } - } - - return _remoteSshd; - } - } } diff --git a/test/Renci.SshNet.IntegrationTests/RemoteSshdConfig.cs b/test/Renci.SshNet.IntegrationTests/RemoteSshdConfig.cs new file mode 100644 index 00000000..39144997 --- /dev/null +++ b/test/Renci.SshNet.IntegrationTests/RemoteSshdConfig.cs @@ -0,0 +1,217 @@ +using Renci.SshNet.TestTools.OpenSSH; + +namespace Renci.SshNet.IntegrationTests +{ + internal sealed class RemoteSshdConfig + { + private const string SshdConfigFilePath = "/etc/ssh/sshd_config"; + private static readonly Encoding Utf8NoBom = new UTF8Encoding(false, true); + + private readonly RemoteSshd _remoteSshd; + private readonly IConnectionInfoFactory _connectionInfoFactory; + private readonly SshdConfig _config; + + public RemoteSshdConfig(RemoteSshd remoteSshd, IConnectionInfoFactory connectionInfoFactory) + { + _remoteSshd = remoteSshd; + _connectionInfoFactory = connectionInfoFactory; + + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var memoryStream = new MemoryStream()) + { + client.Download(SshdConfigFilePath, memoryStream); + + memoryStream.Position = 0; + _config = SshdConfig.LoadFrom(memoryStream, Encoding.UTF8); + } + } + } + + /// + /// Specifies whether challenge-response authentication is allowed. + /// + /// to allow challenge-response authentication. + /// + /// The current instance. + /// + public RemoteSshdConfig WithChallengeResponseAuthentication(bool? value) + { + _config.ChallengeResponseAuthentication = value; + return this; + } + + /// + /// Specifies whether to allow keyboard-interactive authentication. + /// + /// to allow keyboard-interactive authentication. + /// + /// The current instance. + /// + public RemoteSshdConfig WithKeyboardInteractiveAuthentication(bool value) + { + _config.KeyboardInteractiveAuthentication = value; + return this; + } + + /// + /// Specifies whether sshd should print /etc/motd when a user logs in interactively. + /// + /// if sshd should print /etc/motd when a user logs in interactively. + /// + /// The current instance. + /// + public RemoteSshdConfig PrintMotd(bool? value = true) + { + _config.PrintMotd = value; + return this; + } + + /// + /// Specifies whether TCP forwarding is permitted. + /// + /// to allow TCP forwarding. + /// + /// The current instance. + /// + public RemoteSshdConfig AllowTcpForwarding(bool? value = true) + { + _config.AllowTcpForwarding = value; + return this; + } + + public RemoteSshdConfig WithAuthenticationMethods(string user, string authenticationMethods) + { + var sshNetMatch = _config.Matches.Find(m => m.Users.Contains(user)); + if (sshNetMatch is null) + { + sshNetMatch = new Match(new[] { user }, Array.Empty()); + _config.Matches.Add(sshNetMatch); + } + + sshNetMatch.AuthenticationMethods = authenticationMethods; + + return this; + } + + public RemoteSshdConfig ClearCiphers() + { + _config.Ciphers.Clear(); + return this; + } + + public RemoteSshdConfig AddCipher(Cipher cipher) + { + _config.Ciphers.Add(cipher); + return this; + } + + public RemoteSshdConfig ClearKeyExchangeAlgorithms() + { + _config.KeyExchangeAlgorithms.Clear(); + return this; + } + + public RemoteSshdConfig AddKeyExchangeAlgorithm(KeyExchangeAlgorithm keyExchangeAlgorithm) + { + _config.KeyExchangeAlgorithms.Add(keyExchangeAlgorithm); + return this; + } + + public RemoteSshdConfig ClearPublicKeyAcceptedAlgorithms() + { + _config.PublicKeyAcceptedAlgorithms.Clear(); + return this; + } + + public RemoteSshdConfig AddPublicKeyAcceptedAlgorithm(PublicKeyAlgorithm publicKeyAlgorithm) + { + _config.PublicKeyAcceptedAlgorithms.Add(publicKeyAlgorithm); + return this; + } + + public RemoteSshdConfig ClearMessageAuthenticationCodeAlgorithms() + { + _config.MessageAuthenticationCodeAlgorithms.Clear(); + return this; + } + + public RemoteSshdConfig AddMessageAuthenticationCodeAlgorithm(MessageAuthenticationCodeAlgorithm messageAuthenticationCodeAlgorithm) + { + _config.MessageAuthenticationCodeAlgorithms.Add(messageAuthenticationCodeAlgorithm); + return this; + } + + public RemoteSshdConfig ClearHostKeyAlgorithms() + { + _config.HostKeyAlgorithms.Clear(); + return this; + } + + public RemoteSshdConfig AddHostKeyAlgorithm(HostKeyAlgorithm hostKeyAlgorithm) + { + _config.HostKeyAlgorithms.Add(hostKeyAlgorithm); + return this; + } + + public RemoteSshdConfig ClearSubsystems() + { + _config.Subsystems.Clear(); + return this; + } + + public RemoteSshdConfig AddSubsystem(Subsystem subsystem) + { + _config.Subsystems.Add(subsystem); + return this; + } + + public RemoteSshdConfig WithLogLevel(LogLevel logLevel) + { + _config.LogLevel = logLevel; + return this; + } + + public RemoteSshdConfig WithUsePAM(bool usePAM) + { + _config.UsePAM = usePAM; + return this; + } + + public RemoteSshdConfig ClearHostKeyFiles() + { + _config.HostKeyFiles.Clear(); + return this; + } + + public RemoteSshdConfig AddHostKeyFile(string hostKeyFile) + { + _config.HostKeyFiles.Add(hostKeyFile); + return this; + } + + public RemoteSshd Update() + { + using (var client = new ScpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + + using (var memoryStream = new MemoryStream()) + using (var sw = new StreamWriter(memoryStream, Utf8NoBom)) + { + sw.NewLine = "\n"; + _config.SaveTo(sw); + sw.Flush(); + + memoryStream.Position = 0; + + client.Upload(memoryStream, SshdConfigFilePath); + } + } + + return _remoteSshd; + } + } +} diff --git a/test/Renci.SshNet.TestTools.OpenSSH/.editorconfig b/test/Renci.SshNet.TestTools.OpenSSH/.editorconfig new file mode 100644 index 00000000..15b989db --- /dev/null +++ b/test/Renci.SshNet.TestTools.OpenSSH/.editorconfig @@ -0,0 +1,23 @@ +[*.cs] + +#### Meziantou.Analyzer rules #### + +# MA0001: StringComparison is missing +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0001.md +# +# TODO: Re-enable when issues are fixed +dotnet_diagnostic.MA0001.severity = suggestion + +# MA0110: Use the Regex source generator +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0110.md +dotnet_diagnostic.MA0110.severity = none + +#### .NET Compiler Platform analysers rules #### + +# CA1307: Specify StringComparison for clarity +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1307 +dotnet_diagnostic.CA1307.severity = none + +# CA1822: Mark members as static +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1822 +dotnet_code_quality.CA1822.api_surface = none diff --git a/test/Renci.SshNet.TestTools.OpenSSH/Cipher.cs b/test/Renci.SshNet.TestTools.OpenSSH/Cipher.cs index f7d8d884..67650433 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/Cipher.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/Cipher.cs @@ -1,6 +1,6 @@ namespace Renci.SshNet.TestTools.OpenSSH { - public class Cipher + public sealed class Cipher { public static readonly Cipher TripledesCbc = new Cipher("3des-cbc"); public static readonly Cipher Aes128Cbc = new Cipher("aes128-cbc"); diff --git a/test/Renci.SshNet.TestTools.OpenSSH/Formatters/BooleanFormatter.cs b/test/Renci.SshNet.TestTools.OpenSSH/Formatters/BooleanFormatter.cs index 3eab2b19..e58867d7 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/Formatters/BooleanFormatter.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/Formatters/BooleanFormatter.cs @@ -1,6 +1,6 @@ namespace Renci.SshNet.TestTools.OpenSSH.Formatters { - internal class BooleanFormatter + internal sealed class BooleanFormatter { public string Format(bool value) { diff --git a/test/Renci.SshNet.TestTools.OpenSSH/Formatters/Int32Formatter.cs b/test/Renci.SshNet.TestTools.OpenSSH/Formatters/Int32Formatter.cs index 2b823111..dcddb895 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/Formatters/Int32Formatter.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/Formatters/Int32Formatter.cs @@ -2,7 +2,7 @@ namespace Renci.SshNet.TestTools.OpenSSH.Formatters { - internal class Int32Formatter + internal sealed class Int32Formatter { public string Format(int value) { diff --git a/test/Renci.SshNet.TestTools.OpenSSH/Formatters/LogLevelFormatter.cs b/test/Renci.SshNet.TestTools.OpenSSH/Formatters/LogLevelFormatter.cs index f9f4bbf6..9ab89c6a 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/Formatters/LogLevelFormatter.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/Formatters/LogLevelFormatter.cs @@ -1,6 +1,6 @@ namespace Renci.SshNet.TestTools.OpenSSH.Formatters { - internal class LogLevelFormatter + internal sealed class LogLevelFormatter { public string Format(LogLevel logLevel) { diff --git a/test/Renci.SshNet.TestTools.OpenSSH/Formatters/MatchFormatter.cs b/test/Renci.SshNet.TestTools.OpenSSH/Formatters/MatchFormatter.cs index df2968be..d8d7820f 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/Formatters/MatchFormatter.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/Formatters/MatchFormatter.cs @@ -1,6 +1,6 @@ namespace Renci.SshNet.TestTools.OpenSSH.Formatters { - internal class MatchFormatter + internal sealed class MatchFormatter { public string Format(Match match) { diff --git a/test/Renci.SshNet.TestTools.OpenSSH/Formatters/SubsystemFormatter.cs b/test/Renci.SshNet.TestTools.OpenSSH/Formatters/SubsystemFormatter.cs index fcb74e26..91129aeb 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/Formatters/SubsystemFormatter.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/Formatters/SubsystemFormatter.cs @@ -1,6 +1,6 @@ namespace Renci.SshNet.TestTools.OpenSSH.Formatters { - internal class SubsystemFormatter + internal sealed class SubsystemFormatter { public string Format(Subsystem subsystem) { diff --git a/test/Renci.SshNet.TestTools.OpenSSH/HostKeyAlgorithm.cs b/test/Renci.SshNet.TestTools.OpenSSH/HostKeyAlgorithm.cs index 65807f46..23f41478 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/HostKeyAlgorithm.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/HostKeyAlgorithm.cs @@ -1,29 +1,29 @@ namespace Renci.SshNet.TestTools.OpenSSH { - public class HostKeyAlgorithm + public sealed class HostKeyAlgorithm { - public static readonly HostKeyAlgorithm EcdsaSha2Nistp256CertV01OpenSSH = new HostKeyAlgorithm("ecdsa-sha2-nistp256-cert-v01@openssh.com"); - public static readonly HostKeyAlgorithm EcdsaSha2Nistp384CertV01OpenSSH = new HostKeyAlgorithm("ecdsa-sha2-nistp384-cert-v01@openssh.com"); - public static readonly HostKeyAlgorithm EcdsaSha2Nistp521CertV01OpenSSH = new HostKeyAlgorithm("ecdsa-sha2-nistp521-cert-v01@openssh.com"); - public static readonly HostKeyAlgorithm SshEd25519CertV01OpenSSH = new HostKeyAlgorithm("ssh-ed25519-cert-v01@openssh.com"); - public static readonly HostKeyAlgorithm RsaSha2256CertV01OpenSSH = new HostKeyAlgorithm("rsa-sha2-256-cert-v01@openssh.com"); - public static readonly HostKeyAlgorithm RsaSha2512CertV01OpenSSH = new HostKeyAlgorithm("rsa-sha2-512-cert-v01@openssh.com"); - public static readonly HostKeyAlgorithm SshRsaCertV01OpenSSH = new HostKeyAlgorithm("ssh-rsa-cert-v01@openssh.com"); - public static readonly HostKeyAlgorithm EcdsaSha2Nistp256 = new HostKeyAlgorithm("ecdsa-sha2-nistp256"); - public static readonly HostKeyAlgorithm EcdsaSha2Nistp384 = new HostKeyAlgorithm("ecdsa-sha2-nistp384"); - public static readonly HostKeyAlgorithm EcdsaSha2Nistp521 = new HostKeyAlgorithm("ecdsa-sha2-nistp521"); - public static readonly HostKeyAlgorithm SshEd25519 = new HostKeyAlgorithm("ssh-ed25519"); - public static readonly HostKeyAlgorithm RsaSha2512 = new HostKeyAlgorithm("rsa-sha2-512"); - public static readonly HostKeyAlgorithm RsaSha2256 = new HostKeyAlgorithm("rsa-sha2-256"); - public static readonly HostKeyAlgorithm SshRsa = new HostKeyAlgorithm("ssh-rsa"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp256CertV01OpenSSH = new HostKeyAlgorithm("ecdsa-sha2-nistp256-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp384CertV01OpenSSH = new HostKeyAlgorithm("ecdsa-sha2-nistp384-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp521CertV01OpenSSH = new HostKeyAlgorithm("ecdsa-sha2-nistp521-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm SshEd25519CertV01OpenSSH = new HostKeyAlgorithm("ssh-ed25519-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm RsaSha2256CertV01OpenSSH = new HostKeyAlgorithm("rsa-sha2-256-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm RsaSha2512CertV01OpenSSH = new HostKeyAlgorithm("rsa-sha2-512-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm SshRsaCertV01OpenSSH = new HostKeyAlgorithm("ssh-rsa-cert-v01@openssh.com"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp256 = new HostKeyAlgorithm("ecdsa-sha2-nistp256"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp384 = new HostKeyAlgorithm("ecdsa-sha2-nistp384"); + public static readonly HostKeyAlgorithm EcdsaSha2Nistp521 = new HostKeyAlgorithm("ecdsa-sha2-nistp521"); + public static readonly HostKeyAlgorithm SshEd25519 = new HostKeyAlgorithm("ssh-ed25519"); + public static readonly HostKeyAlgorithm RsaSha2512 = new HostKeyAlgorithm("rsa-sha2-512"); + public static readonly HostKeyAlgorithm RsaSha2256 = new HostKeyAlgorithm("rsa-sha2-256"); + public static readonly HostKeyAlgorithm SshRsa = new HostKeyAlgorithm("ssh-rsa"); public static readonly HostKeyAlgorithm SshDss = new HostKeyAlgorithm("ssh-dss"); public HostKeyAlgorithm(string name) { - Name = name; - } + Name = name; + } - public string Name { get; } + public string Name { get; } public override bool Equals(object? obj) { diff --git a/test/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs b/test/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs index 4701103f..e63d8ea1 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/KeyExchangeAlgorithm.cs @@ -1,6 +1,6 @@ namespace Renci.SshNet.TestTools.OpenSSH { - public class KeyExchangeAlgorithm + public sealed class KeyExchangeAlgorithm { public static readonly KeyExchangeAlgorithm DiffieHellmanGroup1Sha1 = new KeyExchangeAlgorithm("diffie-hellman-group1-sha1"); public static readonly KeyExchangeAlgorithm DiffieHellmanGroup14Sha1 = new KeyExchangeAlgorithm("diffie-hellman-group14-sha1"); @@ -16,7 +16,6 @@ public static readonly KeyExchangeAlgorithm Curve25519Sha256Libssh = new KeyExchangeAlgorithm("curve25519-sha256@libssh.org"); public static readonly KeyExchangeAlgorithm Sntrup4591761x25519Sha512 = new KeyExchangeAlgorithm("sntrup4591761x25519-sha512@tinyssh.org"); - public KeyExchangeAlgorithm(string name) { Name = name; diff --git a/test/Renci.SshNet.TestTools.OpenSSH/Match.cs b/test/Renci.SshNet.TestTools.OpenSSH/Match.cs index 16cd5073..d44dc04a 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/Match.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/Match.cs @@ -1,6 +1,6 @@ namespace Renci.SshNet.TestTools.OpenSSH { - public class Match + public sealed class Match { public Match(string[] users, string[] addresses) { @@ -16,6 +16,11 @@ public void WriteTo(TextWriter writer) { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + writer.Write("Match "); if (Users.Length > 0) diff --git a/test/Renci.SshNet.TestTools.OpenSSH/MessageAuthenticationCodeAlgorithm.cs b/test/Renci.SshNet.TestTools.OpenSSH/MessageAuthenticationCodeAlgorithm.cs index 17bf0cf9..edb2466e 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/MessageAuthenticationCodeAlgorithm.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/MessageAuthenticationCodeAlgorithm.cs @@ -1,7 +1,8 @@ namespace Renci.SshNet.TestTools.OpenSSH { - public class MessageAuthenticationCodeAlgorithm + public sealed class MessageAuthenticationCodeAlgorithm { +#pragma warning disable SA1310 // Field names should not contain underscore public static readonly MessageAuthenticationCodeAlgorithm HmacMd5 = new MessageAuthenticationCodeAlgorithm("hmac-md5"); public static readonly MessageAuthenticationCodeAlgorithm HmacMd5_96 = new MessageAuthenticationCodeAlgorithm("hmac-md5-96"); public static readonly MessageAuthenticationCodeAlgorithm HmacRipemd160 = new MessageAuthenticationCodeAlgorithm("hmac-ripemd160"); @@ -20,6 +21,7 @@ public static readonly MessageAuthenticationCodeAlgorithm HmacSha2_512_Etm = new MessageAuthenticationCodeAlgorithm("hmac-sha2-512-etm@openssh.com"); public static readonly MessageAuthenticationCodeAlgorithm Umac64_Etm = new MessageAuthenticationCodeAlgorithm("umac-64-etm@openssh.com"); public static readonly MessageAuthenticationCodeAlgorithm Umac128_Etm = new MessageAuthenticationCodeAlgorithm("umac-128-etm@openssh.com"); +#pragma warning restore SA1310 // Field names should not contain underscore public MessageAuthenticationCodeAlgorithm(string name) { diff --git a/test/Renci.SshNet.TestTools.OpenSSH/PublicKeyAlgorithm.cs b/test/Renci.SshNet.TestTools.OpenSSH/PublicKeyAlgorithm.cs index 292ace7f..24e577b7 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/PublicKeyAlgorithm.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/PublicKeyAlgorithm.cs @@ -1,6 +1,6 @@ namespace Renci.SshNet.TestTools.OpenSSH { - public class PublicKeyAlgorithm + public sealed class PublicKeyAlgorithm { public static readonly PublicKeyAlgorithm SshEd25519 = new PublicKeyAlgorithm("ssh-ed25519"); public static readonly PublicKeyAlgorithm SshEd25519CertV01OpenSSH = new PublicKeyAlgorithm("ssh-ed25519-cert-v01@openssh.com"); diff --git a/test/Renci.SshNet.TestTools.OpenSSH/SshdConfig.cs b/test/Renci.SshNet.TestTools.OpenSSH/SshdConfig.cs index 7dc9f309..5e302571 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/SshdConfig.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/SshdConfig.cs @@ -6,9 +6,10 @@ using Renci.SshNet.TestTools.OpenSSH.Formatters; namespace Renci.SshNet.TestTools.OpenSSH { - public class SshdConfig + public sealed class SshdConfig { - private static readonly Regex MatchRegex = new Regex($@"\s*Match\s+(User\s+(?[\S]+))?\s*(Address\s+(?[\S]+))?\s*", RegexOptions.Compiled); + private static readonly Regex MatchRegex = new Regex($@"\s*Match\s+(User\s+(?[\S]+))?\s*(Address\s+(?[\S]+))?\s*", + RegexOptions.Compiled | RegexOptions.ExplicitCapture); private readonly SubsystemFormatter _subsystemFormatter; private readonly Int32Formatter _int32Formatter; @@ -50,7 +51,7 @@ namespace Renci.SshNet.TestTools.OpenSSH /// /// A list of private host key files used by sshd. /// - public List HostKeyFiles { get; set; } + public List HostKeyFiles { get; } /// /// Gets or sets a value specifying whether challenge-response authentication is allowed. @@ -143,8 +144,83 @@ namespace Renci.SshNet.TestTools.OpenSSH /// public bool? AllowTcpForwarding { get; set; } + public static SshdConfig LoadFrom(Stream stream, Encoding encoding) + { + using (var sr = new StreamReader(stream, encoding)) + { + var sshdConfig = new SshdConfig(); + + Match? currentMatchConfiguration = null; + + string? line; + while ((line = sr.ReadLine()) != null) + { + // Skip empty lines + if (line.Length == 0) + { + continue; + } + + // Skip comments + if (line[0] == '#') + { + continue; + } + + var match = MatchRegex.Match(line); + if (match.Success) + { + var usersGroup = match.Groups["users"]; + var addressesGroup = match.Groups["addresses"]; + var users = usersGroup.Success ? usersGroup.Value.Split(',') : Array.Empty(); + var addresses = addressesGroup.Success ? addressesGroup.Value.Split(',') : Array.Empty(); + + currentMatchConfiguration = new Match(users, addresses); + sshdConfig.Matches.Add(currentMatchConfiguration); + continue; + } + + if (currentMatchConfiguration != null) + { + ProcessMatchOption(currentMatchConfiguration, line); + } + else + { + ProcessGlobalOption(sshdConfig, line); + } + } + + if (sshdConfig.Ciphers == null) + { + // Obtain supported ciphers using ssh -Q cipher + } + + if (sshdConfig.KeyExchangeAlgorithms == null) + { + // Obtain supports key exchange algorithms using ssh -Q kex + } + + if (sshdConfig.HostKeyAlgorithms == null) + { + // Obtain supports host key algorithms using ssh -Q key + } + + if (sshdConfig.MessageAuthenticationCodeAlgorithms == null) + { + // Obtain supported MACs using ssh -Q mac + } + + return sshdConfig; + } + } + public void SaveTo(TextWriter writer) { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + writer.WriteLine("Protocol " + Protocol); writer.WriteLine("Port " + _int32Formatter.Format(Port)); if (HostKeyFiles.Count > 0) @@ -215,84 +291,13 @@ namespace Renci.SshNet.TestTools.OpenSSH { writer.WriteLine("PubkeyAcceptedAlgorithms " + string.Join(",", PublicKeyAcceptedAlgorithms.Select(c => c.Name).ToArray())); } - + foreach (var match in Matches) { _matchFormatter.Format(match, writer); } } - public static SshdConfig LoadFrom(Stream stream, Encoding encoding) - { - using (var sr = new StreamReader(stream, encoding)) - { - var sshdConfig = new SshdConfig(); - - Match? currentMatchConfiguration = null; - - string? line; - while ((line = sr.ReadLine()) != null) - { - // Skip empty lines - if (line.Length == 0) - { - continue; - } - - // Skip comments - if (line[0] == '#') - { - continue; - } - - var match = MatchRegex.Match(line); - if (match.Success) - { - var usersGroup = match.Groups["users"]; - var addressesGroup = match.Groups["addresses"]; - var users = usersGroup.Success ? usersGroup.Value.Split(',') : Array.Empty(); - var addresses = addressesGroup.Success ? addressesGroup.Value.Split(',') : Array.Empty(); - - currentMatchConfiguration = new Match(users, addresses); - sshdConfig.Matches.Add(currentMatchConfiguration); - continue; - } - - if (currentMatchConfiguration != null) - { - ProcessMatchOption(currentMatchConfiguration, line); - } - else - { - ProcessGlobalOption(sshdConfig, line); - } - } - - if (sshdConfig.Ciphers == null) - { - // Obtain supported ciphers using ssh -Q cipher - } - - if (sshdConfig.KeyExchangeAlgorithms == null) - { - // Obtain supports key exchange algorithms using ssh -Q kex - } - - if (sshdConfig.HostKeyAlgorithms == null) - { - // Obtain supports host key algorithms using ssh -Q key - } - - if (sshdConfig.MessageAuthenticationCodeAlgorithms == null) - { - // Obtain supported MACs using ssh -Q mac - } - - - return sshdConfig; - } - } - private static void ProcessGlobalOption(SshdConfig sshdConfig, string line) { var matchOptionRegex = new Regex(@"^\s*(?[\S]+)\s+(?.+?){1}\s*$"); @@ -315,7 +320,7 @@ namespace Renci.SshNet.TestTools.OpenSSH sshdConfig.Port = ToInt(value); break; case "HostKey": - sshdConfig.HostKeyFiles = ParseCommaSeparatedValue(value); + ParseCommaSeparatedValue(sshdConfig.HostKeyFiles, value); break; case "ChallengeResponseAuthentication": sshdConfig.ChallengeResponseAuthentication = ToBool(value); @@ -324,7 +329,7 @@ namespace Renci.SshNet.TestTools.OpenSSH sshdConfig.KeyboardInteractiveAuthentication = ToBool(value); break; case "LogLevel": - sshdConfig.LogLevel = (LogLevel) Enum.Parse(typeof(LogLevel), value, true); + sshdConfig.LogLevel = Enum.Parse(value, ignoreCase: true); break; case "Subsystem": sshdConfig.Subsystems.Add(Subsystem.FromConfig(value)); @@ -382,7 +387,7 @@ namespace Renci.SshNet.TestTools.OpenSSH case "GatewayPorts": break; default: - throw new Exception($"Global option '{name}' is not implemented."); + throw new NotSupportedException($"Global option '{name}' is not supported."); } } @@ -399,10 +404,12 @@ namespace Renci.SshNet.TestTools.OpenSSH { var cipherNames = value.Split(','); var ciphers = new List(cipherNames.Length); + foreach (var cipherName in cipherNames) { ciphers.Add(new Cipher(cipherName.Trim())); } + return ciphers; } @@ -410,21 +417,30 @@ namespace Renci.SshNet.TestTools.OpenSSH { var kexNames = value.Split(','); var keyExchangeAlgorithms = new List(kexNames.Length); + foreach (var kexName in kexNames) { keyExchangeAlgorithms.Add(new KeyExchangeAlgorithm(kexName.Trim())); } + return keyExchangeAlgorithms; } public static List ParsePublicKeyAcceptedAlgorithms(string value) { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + var publicKeyAlgorithmNames = value.Split(','); var publicKeyAlgorithms = new List(publicKeyAlgorithmNames.Length); + foreach (var publicKeyAlgorithmName in publicKeyAlgorithmNames) { publicKeyAlgorithms.Add(new PublicKeyAlgorithm(publicKeyAlgorithmName.Trim())); } + return publicKeyAlgorithms; } @@ -432,10 +448,12 @@ namespace Renci.SshNet.TestTools.OpenSSH { var algorithmNames = value.Split(','); var hostKeyAlgorithms = new List(algorithmNames.Length); + foreach (var algorithmName in algorithmNames) { hostKeyAlgorithms.Add(new HostKeyAlgorithm(algorithmName.Trim())); } + return hostKeyAlgorithms; } @@ -443,10 +461,12 @@ namespace Renci.SshNet.TestTools.OpenSSH { var macNames = value.Split(','); var macAlgorithms = new List(macNames.Length); + foreach (var algorithmName in macNames) { macAlgorithms.Add(new MessageAuthenticationCodeAlgorithm(algorithmName.Trim())); } + return macAlgorithms; } @@ -472,15 +492,14 @@ namespace Renci.SshNet.TestTools.OpenSSH matchConfiguration.AuthenticationMethods = value; break; default: - throw new Exception($"Match option '{name}' is not implemented."); + throw new NotSupportedException($"Match option '{name}' is not supported."); } } - - private static List ParseCommaSeparatedValue(string value) + private static void ParseCommaSeparatedValue(List list, string value) { var values = value.Split(','); - return new List(values); + list.AddRange(values); } private static bool ToBool(string value) @@ -492,7 +511,8 @@ namespace Renci.SshNet.TestTools.OpenSSH case "no": return false; default: - throw new Exception($"Value '{value}' cannot be mapped to a boolean."); + throw new ArgumentException($"Value '{value}' cannot be mapped to a boolean.", + nameof(value)); } } diff --git a/test/Renci.SshNet.TestTools.OpenSSH/Subsystem.cs b/test/Renci.SshNet.TestTools.OpenSSH/Subsystem.cs index 4d115510..140bc35b 100644 --- a/test/Renci.SshNet.TestTools.OpenSSH/Subsystem.cs +++ b/test/Renci.SshNet.TestTools.OpenSSH/Subsystem.cs @@ -2,7 +2,7 @@ namespace Renci.SshNet.TestTools.OpenSSH { - public class Subsystem + public sealed class Subsystem { public Subsystem(string name, string command) { @@ -14,11 +14,6 @@ namespace Renci.SshNet.TestTools.OpenSSH public string Command { get; set; } - public void WriteTo(TextWriter writer) - { - writer.WriteLine(Name + "=" + Command); - } - public static Subsystem FromConfig(string value) { var subSystemValueRegex = new Regex(@"^\s*(?[\S]+)\s+(?.+?){1}\s*$"); @@ -35,7 +30,18 @@ namespace Renci.SshNet.TestTools.OpenSSH return new Subsystem(name, command); } - throw new Exception($"'{value}' not recognized as value for Subsystem."); + throw new ArgumentException($"'{value}' not recognized as value for Subsystem.", + nameof(value)); + } + + public void WriteTo(TextWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteLine(Name + "=" + Command); } } } diff --git a/test/Renci.SshNet.Tests/.editorconfig b/test/Renci.SshNet.Tests/.editorconfig index b94e2911..aa6a9ba3 100644 --- a/test/Renci.SshNet.Tests/.editorconfig +++ b/test/Renci.SshNet.Tests/.editorconfig @@ -1,5 +1,59 @@ [*.cs] +#### Sonar rules #### + +# S108: Nested blocks of code should not be left empty +# https://rules.sonarsource.com/csharp/RSPEC-108/ +dotnet_diagnostic.S108.severity = suggestion + +# S125: Sections of code should not be commented out +# https://rules.sonarsource.com/csharp/RSPEC-125/ +dotnet_diagnostic.S125.severity = none + +# S1144: Unused private types or members should be removed +# https://rules.sonarsource.com/csharp/RSPEC-1144/ +dotnet_diagnostic.S1144.severity = none + +# S1186: Methods should not be empty +# https://rules.sonarsource.com/csharp/RSPEC-1186/ +dotnet_diagnostic.S1186.severity = none + +# S1607: Tests should not be ignored +# https://rules.sonarsource.com/csharp/RSPEC-1607/ +dotnet_diagnostic.S1607.severity = none + +# S2094: Classes should not be empty +# https://rules.sonarsource.com/csharp/RSPEC-2094/ +dotnet_diagnostic.S2094.severity = none + +# S2187: Test classes should contain at least one test case +# https://rules.sonarsource.com/csharp/RSPEC-2187/ +dotnet_diagnostic.S2187.severity = none + +# S2292: Trivial properties should be auto-implemented +# https://rules.sonarsource.com/csharp/RSPEC-2292/ +dotnet_diagnostic.S2292.severity = none + +# S2925: "Thread.Sleep" should not be used in tests +# https://rules.sonarsource.com/csharp/RSPEC-2925/ +dotnet_diagnostic.S2925.severity = none + +# S3415: Assertion arguments should be passed in the correct order +# https://rules.sonarsource.com/csharp/RSPEC-3415/ +dotnet_diagnostic.S3415.severity = none + +# S3881: "IDisposable" should be implemented correctly +# https://rules.sonarsource.com/csharp/RSPEC-3881/ +dotnet_diagnostic.S3881.severity = none + +# S4144: Methods should not have identical implementations +# https://rules.sonarsource.com/csharp/RSPEC-4144/ +dotnet_diagnostic.S4144.severity = none + +# S4158: Empty collections should not be accessed or iterated +# https://rules.sonarsource.com/csharp/RSPEC-4158/ +dotnet_diagnostic.S4158.severity = none + #### SYSLIB diagnostics #### # SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time @@ -9,8 +63,306 @@ dotnet_diagnostic.SYSLIB1045.severity = none ### StyleCop Analyzers rules ### +# SA1000: Keywords must be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1000.md +dotnet_diagnostic.SA1000.severity = suggestion + +# SA1001: Commas must be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1001.md +dotnet_diagnostic.SA1001.severity = suggestion + +# SA1002: Semicolons must be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1002.md +dotnet_diagnostic.SA1002.severity = suggestion + +# SA1004: Documentation lines must begin with single space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1004.md +dotnet_diagnostic.SA1004.severity = suggestion + +# SA1005: Single line comments must begin with single space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1005.md +dotnet_diagnostic.SA1005.severity = suggestion + +# SA1012: Opening braces must be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1012.md +dotnet_diagnostic.SA1012.severity = suggestion + +# SA1013: Closing braces must be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1013.md +dotnet_diagnostic.SA1013.severity = suggestion + +# SA1025: Code must not contain multiple whitespace in a row +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1025.md +dotnet_diagnostic.SA1025.severity = suggestion + +# SA1026: Code must not contain space after new keyword in implicitly typed array allocation +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1026.md +dotnet_diagnostic.SA1026.severity = suggestion + +# SA1028: Code must not contain trailing whitespace +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md +dotnet_diagnostic.SA1028.severity = suggestion + +# SA1111: Closing parenthesis must be on line of last parameter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1111.md +dotnet_diagnostic.SA1111.severity = suggestion + +# SA1117: Parameters must be on same line or separate lines +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md +dotnet_diagnostic.SA1117.severity = suggestion + +# SA1119: Statement must not use unnecessary parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1119.md +dotnet_diagnostic.SA1119.severity = suggestion + +# SA1120: Comments must contain text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1120.md +dotnet_diagnostic.SA1120.severity = suggestion + +# SA1122: Use String.Empty for empty strings +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1122.md +dotnet_diagnostic.SA1122.severity = suggestion + +# SA1123:Do not place regions within elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1123.md +dotnet_diagnostic.SA1123.severity = suggestion + +# SA1124: Do not use regions +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1124.md +dotnet_diagnostic.SA1124.severity = suggestion + +# SA1128: Constructor initializer must be on own line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1128.md +dotnet_diagnostic.SA1128.severity = suggestion + +# SA1129: Do not use default value type constructor +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1129.md +dotnet_diagnostic.SA1129.severity = suggestion + +# SA1133: Do not combine attributes +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1133.md +dotnet_diagnostic.SA1133.severity = suggestion + +# SA1137: Elements should have the same indentation +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1137.md +dotnet_diagnostic.SA1137.severity = suggestion + +# SA1139: Use literals suffix notation instead of casting +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1139.md +dotnet_diagnostic.SA1139.severity = suggestion + +# SA1204: Static elements must appear before instance elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1204.md +dotnet_diagnostic.SA1204.severity = suggestion + +# SA1208: System using directives must be placed before other using directives +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1208.md +dotnet_diagnostic.SA1208.severity = suggestion + +# SA1210: Using directives must be ordered alphabetically by namespace +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1210.md +dotnet_diagnostic.SA1210.severity = suggestion + +# SA1214: Readonly elements must appear before non-readonly elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1214.md +dotnet_diagnostic.SA1214.severity = suggestion + +# SA1306: Field names must begin with lower case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md +dotnet_diagnostic.SA1306.severity = suggestion + +# SA1310: Field names must not contain underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1310.md +dotnet_diagnostic.SA1310.severity = suggestion + +# SA1400: Access modifier must be declared +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1400.md +dotnet_diagnostic.SA1400.severity = suggestion + +# SA1401: Fields must be private +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md +dotnet_diagnostic.SA1401.severity = suggestion + +# SA1402: File may only contain a single type +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1402.md +dotnet_diagnostic.SA1402.severity = suggestion + +# SA1404: Code analysis suppression must have justification +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1404.md +dotnet_diagnostic.SA1404.severity = suggestion + +# SA1411: Attribute constructor must not use unnecessary parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1411.md +dotnet_diagnostic.SA1411.severity = suggestion + +# SA1500: Braces for multiline statements must not share line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1500.md +dotnet_diagnostic.SA1500.severity = suggestion + +# SA1501: Statement must not be on single line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1501.md +dotnet_diagnostic.SA1501.severity = suggestion + +# SA1505: Opening braces must not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1505.md +dotnet_diagnostic.SA1505.severity = suggestion + +# SA1507: Code must not contain multiple blank lines in a row +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1507.md +dotnet_diagnostic.SA1507.severity = suggestion + +# SA1508: Closing braces must not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1508.md +dotnet_diagnostic.SA1508.severity = suggestion + +# SA1512: Single line comments must not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1512.md +dotnet_diagnostic.SA1512.severity = suggestion + +# SA1513: Closing brace must be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md +dotnet_diagnostic.SA1513.severity = suggestion + +# SA1515: Single line comment must be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1515.md +dotnet_diagnostic.SA1515.severity = suggestion + +# SA1518: Use line endings correctly at end of file +https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1518.md +dotnet_diagnostic.SA1518.severity = suggestion + +# SA1636: File header copyright text must match +https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1636.md +dotnet_diagnostic.SA1636.severity = suggestion + +# SA1642: Constructor summary documentation must begin with standard text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1642.md +dotnet_diagnostic.SA1642.severity = suggestion + +# SA1649: File name must match type name +https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1649.md +dotnet_diagnostic.SA1649.severity = suggestion + +#### Meziantou.Analyzer rules #### + +# MA0001: StringComparison is missing +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0001.md +dotnet_diagnostic.MA0001.severity = suggestion + +# MA0003: Add parameter name to improve readability +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0003.md +dotnet_diagnostic.MA0003.severity = suggestion + +# MA0004: Use Task.ConfigureAwait(false) +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0004.md +dotnet_diagnostic.MA0004.severity = suggestion + +# MA0005: Use Array.Empty() +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0005.md +dotnet_diagnostic.MA0005.severity = suggestion + +# MA0011: IFormatProvider is missing +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0011.md +dotnet_diagnostic.MA0011.severity = suggestion + +# MA0015: Specify the parameter name in ArgumentException +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0015.md +dotnet_diagnostic.MA0015.severity = suggestion + +# MA0019: Use EventArgs.Empty +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0019.md +dotnet_diagnostic.MA0019.severity = suggestion + +# MA0035: Do not use dangerous threading methods +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0035.md +dotnet_diagnostic.MA0035.severity = suggestion + +# MA0040: Forward the CancellationToken parameter to methods that take one +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0040.md +dotnet_diagnostic.MA0040.severity = suggestion + +# MA0046: Use EventHandler to declare events +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0046.md +dotnet_diagnostic.MA0046.severity = suggestion + +# MA0053: Make class sealed +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0053.md +dotnet_diagnostic.MA0053.severity = suggestion + +# MA0060: The value returned by Stream.Read/Stream.ReadAsync is not used +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0060.md +dotnet_diagnostic.MA0060.severity = suggestion + +# MA0075: Do not use implicit culture-sensitive ToString +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0075.md +dotnet_diagnostic.MA0075.severity = suggestion + +# MA0099 - Use Explicit enum value instead of 0 +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0099.md +dotnet_diagnostic.MA0099.severity = suggestion + +# MA0110: Use the Regex source generator +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0110.md +dotnet_diagnostic.MA0110.severity = suggestion + #### .NET Compiler Platform analysers rules #### +# CA1031: Do not catch general exception types +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1031 +dotnet_diagnostic.CA1031.severity = suggestion + +# CA1062: Validate arguments of public methods +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1062 +dotnet_diagnostic.CA1062.severity = suggestion + +# CA1063: Implement IDisposable correctly +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1063 +dotnet_diagnostic.CA1063.severity = suggestion + +# CA1307: Specify StringComparison for clarity +https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1307 +dotnet_diagnostic.CA1307.severity = suggestion + +# CA1806: Do not ignore method results +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1806 +dotnet_diagnostic.CA1806.severity = suggestion + +# CA1812: Avoid uninstantiated internal classes +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1812 +dotnet_diagnostic.CA1812.severity = suggestion + +# CA1822: Mark members as static +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1822 +dotnet_diagnostic.CA1822.severity = suggestion + +# CA1825: Avoid zero-length array allocations +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1825 +dotnet_diagnostic.CA1825.severity = suggestion + +# CA1859: Use concrete types when possible for improved performance +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1859 +dotnet_diagnostic.CA1859.severity = suggestion + +# CA2000: Dispose objects before losing scope +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2000 +dotnet_diagnostic.CA2000.severity = suggestion + +# CA2007: Do not directly await a Task +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2007 +dotnet_diagnostic.CA2007.severity = suggestion + +# CA2201: Do not raise reserved exception types +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2201 +dotnet_diagnostic.CA2201.severity = suggestion + +# CA2213: Disposable fields should be disposed +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2213 +dotnet_diagnostic.CA2213.severity = suggestion + +# CA2227: Collection properties should be read only +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 +dotnet_diagnostic.CA2227.severity = suggestion + # IDE0007: Use var instead of explicit type # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0007 dotnet_diagnostic.IDE0007.severity = suggestion diff --git a/test/Renci.SshNet.Tests/Classes/Common/SshDataTest.cs b/test/Renci.SshNet.Tests/Classes/Common/SshDataTest.cs index e5b7bc86..03b48912 100644 --- a/test/Renci.SshNet.Tests/Classes/Common/SshDataTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Common/SshDataTest.cs @@ -1,5 +1,7 @@ using System; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Renci.SshNet.Common; namespace Renci.SshNet.Tests.Classes.Common @@ -86,6 +88,55 @@ namespace Renci.SshNet.Tests.Classes.Common Assert.AreEqual(two, request.ValueTwo); } + [TestMethod] + public void ReadBytes_Length_LengthEqualsNumberOfBytesAvailable() + { + MySshData sshData = new MySshData(); + + sshData.Load(new byte[] { 0x01, 0x0d, 0x0a, 0x07, 0x0b }, 0, 4); + sshData.Write(new byte[] { 0x09, 0x03, 0x06, 0x0b }, 0, 3); + + var bytes = sshData.ReadBytes(1); + + CollectionAssert.AreEqual(new byte[] { 0x07 }, bytes); + } + + [TestMethod] + public void ReadBytes_Length_LengthIsGreaterThanNumberOfBytesAvailable() + { + MySshData sshData = new MySshData(); + + sshData.Load(new byte[] { 0x01, 0x0d, 0x0a }, 0, 3); + + try + { + sshData.ReadBytes(4); + Assert.Fail(); + } + catch (ArgumentOutOfRangeException ex) + { + Assert.AreEqual(typeof(ArgumentOutOfRangeException), ex.GetType()); + Assert.IsNull(ex.InnerException); + Assert.AreEqual("length", ex.ParamName); + } + } + + [TestMethod] + public void ReadBytes_Length_LengthIsLessThanNumberOfBytesAvailable() + { + MySshData sshData = new MySshData(); + + sshData.Load(new byte[] { 0x05, 0x07, 0x02, 0x0b, 0x0a, 0x0d }, 0, 5); + + var bytes = sshData.ReadBytes(2); + + CollectionAssert.AreEqual(new byte[] { 0x05, 0x07 }, bytes); + + bytes = sshData.ReadBytes(3); + + CollectionAssert.AreEqual(new byte[] { 0x02, 0x0b, 0x0a }, bytes); + } + private class BoolSshData : SshData { private readonly bool _value; @@ -145,5 +196,26 @@ namespace Renci.SshNet.Tests.Classes.Common Write(ValueTwo); } } + + private class MySshData : SshData + { + protected override void LoadData() + { + } + + protected override void SaveData() + { + } + + public new byte[] ReadBytes(int length) + { + return base.ReadBytes(length); + } + + public new void Write(byte[] buffer, int offset, int count) + { + base.Write(buffer, offset, count); + } + } } } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessRead.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessRead.cs index 09423c6b..e366914c 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessRead.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessRead.cs @@ -2,6 +2,7 @@ using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -48,8 +49,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(string.Format("{0} mode can be requested only when combined with write-only access.", _fileMode), _actualException.Message); - Assert.IsNull(_actualException.ParamName); + ArgumentExceptionAssert.MessageEquals(string.Format("{0} mode can be requested only when combined with write-only access.", _fileMode), _actualException); + Assert.AreEqual("mode", _actualException.ParamName); } } } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessReadWrite.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessReadWrite.cs index 8f31995c..d4b311ff 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessReadWrite.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessReadWrite.cs @@ -2,6 +2,7 @@ using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -48,8 +49,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(string.Format("{0} mode can be requested only when combined with write-only access.", _fileMode), _actualException.Message); - Assert.IsNull(_actualException.ParamName); + ArgumentExceptionAssert.MessageEquals(string.Format("{0} mode can be requested only when combined with write-only access.", _fileMode), _actualException); + Assert.AreEqual("mode", _actualException.ParamName); } } } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeCreateNew_FileAccessRead.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeCreateNew_FileAccessRead.cs index 47cabd2a..5fd18e51 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeCreateNew_FileAccessRead.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeCreateNew_FileAccessRead.cs @@ -4,6 +4,7 @@ using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -50,8 +51,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException.Message); - Assert.IsNull(_actualException.ParamName); + ArgumentExceptionAssert.MessageEquals(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException); + Assert.AreEqual("mode", _actualException.ParamName); } } } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeCreate_FileAccessRead.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeCreate_FileAccessRead.cs index 29e4956a..c70b3239 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeCreate_FileAccessRead.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeCreate_FileAccessRead.cs @@ -4,6 +4,7 @@ using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -50,8 +51,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException.Message); - Assert.IsNull(_actualException.ParamName); + ArgumentExceptionAssert.MessageEquals(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException); + Assert.AreEqual("mode", _actualException.ParamName); } } } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeTruncate_FileAccessRead.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeTruncate_FileAccessRead.cs index 2ed97778..9e520a59 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeTruncate_FileAccessRead.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeTruncate_FileAccessRead.cs @@ -2,6 +2,7 @@ using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -48,8 +49,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException.Message); - Assert.IsNull(_actualException.ParamName); + ArgumentExceptionAssert.MessageEquals(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException); + Assert.AreEqual("mode", _actualException.ParamName); } } } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessRead.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessRead.cs index 14a2338b..242755a0 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessRead.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessRead.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -49,8 +50,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(string.Format("{0} mode can be requested only when combined with write-only access.", _fileMode), _actualException.Message); - Assert.IsNull(_actualException.ParamName); + ArgumentExceptionAssert.MessageEquals(string.Format("{0} mode can be requested only when combined with write-only access.", _fileMode), _actualException); + Assert.AreEqual("mode", _actualException.ParamName); } } } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessReadWrite.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessReadWrite.cs index c58f3387..776cec41 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessReadWrite.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeAppend_FileAccessReadWrite.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -49,8 +50,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(string.Format("{0} mode can be requested only when combined with write-only access.", _fileMode), _actualException.Message); - Assert.IsNull(_actualException.ParamName); + ArgumentExceptionAssert.MessageEquals(string.Format("{0} mode can be requested only when combined with write-only access.", _fileMode), _actualException); + Assert.AreEqual("mode", _actualException.ParamName); } } } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessRead.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessRead.cs index 1d8b792d..900f1b15 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessRead.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreateNew_FileAccessRead.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -51,8 +52,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException.Message); - Assert.IsNull(_actualException.ParamName); + ArgumentExceptionAssert.MessageEquals(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException); + Assert.AreEqual("mode", _actualException.ParamName); } } } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessRead.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessRead.cs index d253c30e..6924da97 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessRead.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeCreate_FileAccessRead.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -51,8 +52,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException.Message); - Assert.IsNull(_actualException.ParamName); + ArgumentExceptionAssert.MessageEquals(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException); + Assert.AreEqual("mode", _actualException.ParamName); } } } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessRead.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessRead.cs index 660bd1a1..276455b7 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessRead.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_OpenAsync_FileModeTruncate_FileAccessRead.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Sftp; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -51,8 +52,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp { Assert.IsNotNull(_actualException); Assert.IsNull(_actualException.InnerException); - Assert.AreEqual(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException.Message); - Assert.IsNull(_actualException.ParamName); + ArgumentExceptionAssert.MessageEquals(string.Format("Combining {0}: {1} with {2}: {3} is invalid.", nameof(FileMode), _fileMode, nameof(FileAccess), _fileAccess), _actualException); + Assert.AreEqual("mode", _actualException.ParamName); } } }