Procházet zdrojové kódy

Pass full remote file path to scp command when uploading or downloading a file, and use zero-length pass in C directive. Combined, this ensure the SCP server will reject a remote file path that points to a directory.

Fixes issue #286.
Gert Driesen před 8 roky
rodič
revize
266dbaefe6

+ 4 - 1
src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

@@ -197,6 +197,9 @@
     <Compile Include="..\Renci.SshNet\Common\PortForwardEventArgs.cs">
       <Link>Common\PortForwardEventArgs.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Common\PosixPath.cs">
+      <Link>Common\PosixPath.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Common\ProxyException.cs">
       <Link>Common\ProxyException.cs</Link>
     </Compile>
@@ -953,7 +956,7 @@
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 1 - 1
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs

@@ -48,7 +48,7 @@ namespace Renci.SshNet.Tests.Classes
             _fileName = CreateTemporaryFile(new byte[] {1});
             _connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
             _fileInfo = new FileInfo(_fileName);
-            _path = random.Next().ToString(CultureInfo.InvariantCulture);
+            _path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
             _quotedPath = _path.ShellQuote();
             _uploadingRegister = new List<ScpUploadEventArgs>();
 

+ 2 - 6
src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs

@@ -56,7 +56,7 @@ namespace Renci.SshNet.Tests.Classes
             _fileName = CreateTemporaryFile(_fileContent);
             _connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
             _fileInfo = new FileInfo(_fileName);
-            _path = random.Next().ToString(CultureInfo.InvariantCulture);
+            _path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
             _quotedPath = _path.ShellQuote();
             _uploadingRegister = new List<ScpUploadEventArgs>();
         }
@@ -86,11 +86,7 @@ namespace Renci.SshNet.Tests.Classes
             _pipeStreamMock.InSequence(sequence).Setup(p => p.ReadByte()).Returns(0);
             _channelSessionMock.InSequence(sequence)
                 .Setup(p => p.SendData(It.Is<byte[]>(b => b.SequenceEqual(CreateData(
-                    string.Format("C0644 {0} {1}\n",
-                        _fileInfo.Length,
-                        Path.GetFileName(_fileName)
-                        )
-                    )))));
+                    string.Format("C0644 {0} {1}\n", _fileInfo.Length, string.Empty))))));
             _pipeStreamMock.InSequence(sequence).Setup(p => p.ReadByte()).Returns(0);
             _channelSessionMock.InSequence(sequence)
                 .Setup(

+ 24 - 0
src/Renci.SshNet/Common/PosixPath.cs

@@ -0,0 +1,24 @@
+namespace Renci.SshNet.Common
+{
+    internal class PosixPath
+    {
+        /// <summary>
+        /// Gets the file name part of a given POSIX path.
+        /// </summary>
+        /// <param name="path">The POSIX path to get the file name for.</param>
+        /// <returns>
+        /// The file name part of <paramref name="path"/>.
+        /// </returns>
+        /// <remarks>
+        /// If <paramref name="path"/> contains no forward slash or has a trailing
+        /// forward slash, then <paramref name="path"/> is returned.
+        /// </remarks>
+        public static string GetFileName(string path)
+        {
+            var pathEnd = path.LastIndexOf('/');
+            if (pathEnd == -1 || pathEnd == path.Length - 1)
+                return path;
+            return path.Substring(pathEnd + 1);
+        }
+    }
+}

+ 1 - 0
src/Renci.SshNet/Renci.SshNet.csproj

@@ -97,6 +97,7 @@
     <Compile Include="Common\ChannelRequestEventArgs.cs" />
     <Compile Include="Common\CountdownEvent.cs" />
     <Compile Include="Common\Pack.cs" />
+    <Compile Include="Common\PosixPath.cs" />
     <Compile Include="Common\ProxyException.cs">
       <SubType>Code</SubType>
     </Compile>

+ 48 - 46
src/Renci.SshNet/ScpClient.NET.cs

@@ -18,9 +18,10 @@ namespace Renci.SshNet
         /// Uploads the specified file to the remote host.
         /// </summary>
         /// <param name="fileInfo">The file system info.</param>
-        /// <param name="path">The path.</param>
+        /// <param name="path">A relative or absolute path for the remote file.</param>
         /// <exception cref="ArgumentNullException"><paramref name="fileInfo" /> is <c>null</c>.</exception>
         /// <exception cref="ArgumentException"><paramref name="path"/> is <c>null</c> or empty.</exception>
+        /// <exception cref="ScpException">A directory with the specified path exists on the remote host.</exception>
         public void Upload(FileInfo fileInfo, string path)
         {
             if (fileInfo == null)
@@ -36,10 +37,14 @@ namespace Renci.SshNet
 
                 if (!channel.SendExecRequest(string.Format("scp -t {0}", path.ShellQuote())))
                     throw new SshException("Secure copy execution request was rejected by the server. Please consult the server logs.");
-
                 CheckReturnCode(input);
 
-                InternalUpload(channel, input, fileInfo);
+                using (var source = fileInfo.OpenRead())
+                {
+                    UploadTimes(channel, input, fileInfo);
+                    UploadFileModeAndName(channel, input, source.Length, string.Empty);
+                    UploadFileContent(channel, input, source, fileInfo.Name);
+                }
             }
         }
 
@@ -47,9 +52,10 @@ namespace Renci.SshNet
         /// Uploads the specified directory to the remote host.
         /// </summary>
         /// <param name="directoryInfo">The directory info.</param>
-        /// <param name="path">The path.</param>
+        /// <param name="path">A relative or absolute path for the remote directory.</param>
         /// <exception cref="ArgumentNullException">fileSystemInfo</exception>
         /// <exception cref="ArgumentException"><paramref name="path"/> is <c>null</c> or empty.</exception>
+        /// <exception cref="ScpException"><paramref name="path"/> exists on the remote host, and is not a directory.</exception>
         public void Upload(DirectoryInfo directoryInfo, string path)
         {
             if (directoryInfo == null)
@@ -67,17 +73,9 @@ namespace Renci.SshNet
                 channel.SendExecRequest(string.Format("scp -rt {0}", path.ShellQuote()));
                 CheckReturnCode(input);
 
-                // set last write and last access time on specified remote path
-                InternalSetTimestamp(channel, input, directoryInfo.LastWriteTimeUtc, directoryInfo.LastAccessTimeUtc);
-                SendData(channel, string.Format("D0755 0 {0}\n", "."));
-                CheckReturnCode(input);
-
-                // recursively upload files and directories in specified remote path
-                InternalUpload(channel, input, directoryInfo);
-
-                // terminate upload of specified remote path
-                SendData(channel, "E\n");
-                CheckReturnCode(input);
+                UploadTimes(channel, input, directoryInfo);
+                UploadDirectoryModeAndName(channel, input, ".");
+                UploadDirectoryContent(channel, input, directoryInfo);
             }
         }
 
@@ -88,6 +86,7 @@ namespace Renci.SshNet
         /// <param name="fileInfo">Local file information.</param>
         /// <exception cref="ArgumentNullException"><paramref name="fileInfo"/> is <c>null</c>.</exception>
         /// <exception cref="ArgumentException"><paramref name="filename"/> is <c>null</c> or empty.</exception>
+        /// <exception cref="ScpException"><paramref name="filename"/> exists on the remote host, and is not a regular file.</exception>
         public void Download(string filename, FileInfo fileInfo)
         {
             if (string.IsNullOrEmpty(filename))
@@ -104,7 +103,7 @@ namespace Renci.SshNet
                 // Send channel command request
                 channel.SendExecRequest(string.Format("scp -pf {0}", filename.ShellQuote()));
                 // Send reply
-                SendConfirmation(channel);
+                SendSuccessConfirmation(channel);
 
                 InternalDownload(channel, input, fileInfo);
             }
@@ -117,6 +116,7 @@ namespace Renci.SshNet
         /// <param name="directoryInfo">Local directory information.</param>
         /// <exception cref="ArgumentException"><paramref name="directoryName"/> is <c>null</c> or empty.</exception>
         /// <exception cref="ArgumentNullException"><paramref name="directoryInfo"/> is <c>null</c>.</exception>
+        /// <exception cref="ScpException">File or directory with the specified path does not exist on the remote host.</exception>
         public void Download(string directoryName, DirectoryInfo directoryInfo)
         {
             if (string.IsNullOrEmpty(directoryName))
@@ -133,51 +133,53 @@ namespace Renci.SshNet
                 // Send channel command request
                 channel.SendExecRequest(string.Format("scp -prf {0}", directoryName.ShellQuote()));
                 // Send reply
-                SendConfirmation(channel);
+                SendSuccessConfirmation(channel);
 
                 InternalDownload(channel, input, directoryInfo);
             }
         }
 
         /// <summary>
-        /// Uploads the file in the active directory context, and set
+        /// Upload the files and subdirectories in the specified directory.
         /// </summary>
         /// <param name="channel">The channel to perform the upload in.</param>
         /// <param name="input">A <see cref="Stream"/> from which any feedback from the server can be read.</param>
-        /// <param name="fileInfo">The file to upload.</param>
-        private void InternalUpload(IChannelSession channel, Stream input, FileInfo fileInfo)
-        {
-            using (var source = fileInfo.OpenRead())
-            {
-                // set the last write and last access time for the next file uploaded
-                InternalSetTimestamp(channel, input, fileInfo.LastWriteTimeUtc, fileInfo.LastAccessTimeUtc);
-                // upload the actual file
-                InternalUpload(channel, input, source, fileInfo.Name);
-            }
-        }
-
-        private void InternalUpload(IChannelSession channel, Stream input, DirectoryInfo directoryInfo)
+        /// <param name="directoryInfo">The directory to upload.</param>
+        private void UploadDirectoryContent(IChannelSession channel, Stream input, DirectoryInfo directoryInfo)
         {
             //  Upload files
             var files = directoryInfo.GetFiles();
             foreach (var file in files)
             {
-                InternalUpload(channel, input, file);
+                using (var source = file.OpenRead())
+                {
+                    UploadTimes(channel, input, file);
+                    UploadFileModeAndName(channel, input, source.Length, file.Name);
+                    UploadFileContent(channel, input, source, file.Name);
+                }
             }
 
             //  Upload directories
             var directories = directoryInfo.GetDirectories();
             foreach (var directory in directories)
             {
-                InternalSetTimestamp(channel, input, directory.LastWriteTimeUtc, directory.LastAccessTimeUtc);
-                SendData(channel, string.Format("D0755 0 {0}\n", directory.Name));
-                CheckReturnCode(input);
+                UploadTimes(channel, input, directory);
+                UploadDirectoryModeAndName(channel, input, directory.Name);
+                UploadDirectoryContent(channel, input, directory);
+            }
 
-                InternalUpload(channel, input, directory);
+            // Mark upload of current directory complete
+            SendData(channel, "E\n");
+            CheckReturnCode(input);
+        }
 
-                SendData(channel, "E\n");
-                CheckReturnCode(input);
-            }
+        /// <summary>
+        /// Sets mode and name of the directory being upload.
+        /// </summary>
+        private void UploadDirectoryModeAndName(IChannelSession channel, Stream input, string directoryName)
+        {
+            SendData(channel, string.Format("D0755 0 {0}\n", directoryName));
+            CheckReturnCode(input);
         }
 
         private void InternalDownload(IChannelSession channel, Stream input, FileSystemInfo fileSystemInfo)
@@ -195,7 +197,7 @@ namespace Renci.SshNet
 
                 if (message == "E")
                 {
-                    SendConfirmation(channel); //  Send reply
+                    SendSuccessConfirmation(channel); //  Send reply
 
                     directoryCounter--;
 
@@ -209,7 +211,7 @@ namespace Renci.SshNet
                 var match = DirectoryInfoRe.Match(message);
                 if (match.Success)
                 {
-                    SendConfirmation(channel); //  Send reply
+                    SendSuccessConfirmation(channel); //  Send reply
 
                     //  Read directory
                     var filename = match.Result("${filename}");
@@ -217,7 +219,7 @@ namespace Renci.SshNet
                     DirectoryInfo newDirectoryInfo;
                     if (directoryCounter > 0)
                     {
-                        newDirectoryInfo = Directory.CreateDirectory(string.Format("{0}{1}{2}", currentDirectoryFullName, Path.DirectorySeparatorChar, filename));
+                        newDirectoryInfo = Directory.CreateDirectory(Path.Combine(currentDirectoryFullName, filename));
                         newDirectoryInfo.LastAccessTime = accessedTime;
                         newDirectoryInfo.LastWriteTime = modifiedTime;
                     }
@@ -237,7 +239,7 @@ namespace Renci.SshNet
                 if (match.Success)
                 {
                     //  Read file
-                    SendConfirmation(channel); //  Send reply
+                    SendSuccessConfirmation(channel); //  Send reply
 
                     var length = long.Parse(match.Result("${length}"));
                     var fileName = match.Result("${filename}");
@@ -245,7 +247,7 @@ namespace Renci.SshNet
                     var fileInfo = fileSystemInfo as FileInfo;
 
                     if (fileInfo == null)
-                        fileInfo = new FileInfo(string.Format("{0}{1}{2}", currentDirectoryFullName, Path.DirectorySeparatorChar, fileName));
+                        fileInfo = new FileInfo(Path.Combine(currentDirectoryFullName, fileName));
 
                     using (var output = fileInfo.OpenWrite())
                     {
@@ -264,7 +266,7 @@ namespace Renci.SshNet
                 if (match.Success)
                 {
                     //  Read timestamp
-                    SendConfirmation(channel); //  Send reply
+                    SendSuccessConfirmation(channel); //  Send reply
 
                     var mtime = long.Parse(match.Result("${mtime}"));
                     var atime = long.Parse(match.Result("${atime}"));
@@ -275,7 +277,7 @@ namespace Renci.SshNet
                     continue;
                 }
 
-                SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message));
+                SendErrorConfirmation(channel, string.Format("\"{0}\" is not valid protocol message.", message));
             }
         }
     }

+ 70 - 37
src/Renci.SshNet/ScpClient.cs

@@ -22,7 +22,7 @@ namespace Renci.SshNet
     /// <list type="bullet">
     ///   <item>
     ///     <description>Recursive download (-prf) does not deal well with specific UTF-8 and newline characters.</description>
-    ///     <description>Recursive update does not support empty path for uploading to home directorydeal well with specific UTF-8 and newline characters.</description>
+    ///     <description>Recursive update does not support empty path for uploading to home directory.</description>
     ///   </item>
     /// </list>
     /// </para>
@@ -30,6 +30,8 @@ namespace Renci.SshNet
     public partial class ScpClient : BaseClient
     {
         private static readonly Regex FileInfoRe = new Regex(@"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");
+        private static readonly byte[] SuccessConfirmationCode = {0};
+        private static readonly byte[] ErrorConfirmationCode = { 1 };
 
         /// <summary>
         /// Gets or sets the operation timeout.
@@ -167,8 +169,9 @@ namespace Renci.SshNet
         /// <summary>
         /// Uploads the specified stream to the remote host.
         /// </summary>
-        /// <param name="source">Stream to upload.</param>
-        /// <param name="path">Remote host file name.</param>
+        /// <param name="source">The <see cref="Stream"/> to upload.</param>
+        /// <param name="path">A relative or absolute path for the remote file.</param>
+        /// <exception cref="ScpException">A directory with the specified path exists on the remote host.</exception>
         public void Upload(Stream source, string path)
         {
             using (var input = ServiceFactory.CreatePipeStream())
@@ -177,30 +180,27 @@ namespace Renci.SshNet
                 channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
                 channel.Open();
 
-                var pathEnd = path.LastIndexOfAny(new[] { '\\', '/' });
-                if (pathEnd != -1)
-                {
-                    // split the path from the file
-                    var pathOnly = path.Substring(0, pathEnd);
-                    var fileOnly = path.Substring(pathEnd + 1);
-                    //  Send channel command request
-                    channel.SendExecRequest(string.Format("scp -t \"{0}\"", pathOnly));
-                    CheckReturnCode(input);
-
-                    path = fileOnly;
-                }
+                // pass the full path to ensure the server does not create the directory part
+                // as a file in case the directory does not exist
+                if (!channel.SendExecRequest(string.Format("scp -t {0}", path.ShellQuote())))
+                    return;
+                CheckReturnCode(input);
 
-                InternalUpload(channel, input, source, path);
+                // specify a zero-length file name to avoid creating a file with absolute
+                // path '<path>/<filename part of path>' if directory '<path>' already exists
+                UploadFileModeAndName(channel, input, source.Length, string.Empty);
+                UploadFileContent(channel, input, source, PosixPath.GetFileName(path));
             }
         }
 
         /// <summary>
         /// Downloads the specified file from the remote host to the stream.
         /// </summary>
-        /// <param name="filename">Remote host file name.</param>
-        /// <param name="destination">The stream where to download remote file.</param>
+        /// <param name="filename">A relative or absolute path for the remote file.</param>
+        /// <param name="destination">The <see cref="Stream"/> to download the remote file to.</param>
         /// <exception cref="ArgumentException"><paramref name="filename"/> is <c>null</c> or contains only whitespace characters.</exception>
         /// <exception cref="ArgumentNullException"><paramref name="destination"/> is <c>null</c>.</exception>
+        /// <exception cref="ScpException"><paramref name="filename"/> exists on the remote host, and is not a regular file.</exception>
         /// <remarks>
         /// Method calls made by this method to <paramref name="destination"/>, may under certain conditions result
         /// in exceptions thrown by the stream.
@@ -221,7 +221,7 @@ namespace Renci.SshNet
 
                 //  Send channel command request
                 channel.SendExecRequest(string.Format("scp -f {0}", filename.ShellQuote()));
-                SendConfirmation(channel); //  Send reply
+                SendSuccessConfirmation(channel); //  Send reply
 
                 var message = ReadString(input);
                 var match = FileInfoRe.Match(message);
@@ -229,7 +229,7 @@ namespace Renci.SshNet
                 if (match.Success)
                 {
                     //  Read file
-                    SendConfirmation(channel); //  Send reply
+                    SendSuccessConfirmation(channel); //  Send reply
 
                     var length = long.Parse(match.Result("${length}"));
                     var fileName = match.Result("${filename}");
@@ -238,28 +238,61 @@ namespace Renci.SshNet
                 }
                 else
                 {
-                    SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message));
+                    SendErrorConfirmation(channel, string.Format("\"{0}\" is not valid protocol message.", message));
                 }
             }
         }
 
-        private void InternalSetTimestamp(IChannelSession channel, Stream input, DateTime lastWriteTime, DateTime lastAccessime)
+        /// <summary>
+        /// Uploads the <see cref="FileSystemInfo.LastWriteTimeUtc"/> and <see cref="FileSystemInfo.LastAccessTimeUtc"/>
+        /// of the next file or directory to upload.
+        /// </summary>
+        /// <param name="channel">The channel to perform the upload in.</param>
+        /// <param name="input">A <see cref="Stream"/> from which any feedback from the server can be read.</param>
+        /// <param name="fileOrDirectory">The file or directory to upload.</param>
+        private void UploadTimes(IChannelSession channel, Stream input, FileSystemInfo fileOrDirectory)
         {
             var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
-            var modificationSeconds = (long) (lastWriteTime - zeroTime).TotalSeconds;
-            var accessSeconds = (long) (lastAccessime - zeroTime).TotalSeconds;
+            var modificationSeconds = (long) (fileOrDirectory.LastWriteTimeUtc - zeroTime).TotalSeconds;
+            var accessSeconds = (long) (fileOrDirectory.LastAccessTimeUtc - zeroTime).TotalSeconds;
             SendData(channel, string.Format("T{0} 0 {1} 0\n", modificationSeconds, accessSeconds));
             CheckReturnCode(input);
         }
 
-        private void InternalUpload(IChannelSession channel, Stream input, Stream source, string filename)
+        /// <summary>
+        /// Sets mode, size and name of file being upload.
+        /// </summary>
+        /// <param name="channel">The channel to perform the upload in.</param>
+        /// <param name="input">A <see cref="Stream"/> from which any feedback from the server can be read.</param>
+        /// <param name="fileSize">The size of the content to upload.</param>
+        /// <param name="serverFileName">The name of the file, without path, to which the content is to be uploaded.</param>
+        /// <remarks>
+        /// <para>
+        /// When the SCP transfer is already initiated for a file, a zero-length <see cref="string"/> should
+        /// be specified for <paramref name="serverFileName"/>. This prevents the server from uploading the
+        /// content to a file with path <c>&lt;file path&gt;/<paramref name="serverFileName"/></c> if there's
+        /// already a directory with this path, and allows us to receive an error response.
+        /// </para>
+        /// </remarks>
+        private void UploadFileModeAndName(IChannelSession channel, Stream input, long fileSize, string serverFileName)
         {
-            var length = source.Length;
-
-            // specify permissions, length and name of file being upload
-            SendData(channel, string.Format("C0644 {0} {1}\n", length, filename));
+            SendData(channel, string.Format("C0644 {0} {1}\n", fileSize, serverFileName));
             CheckReturnCode(input);
+        }
 
+        /// <summary>
+        /// Uploads the content of a file.
+        /// </summary>
+        /// <param name="channel">The channel to perform the upload in.</param>
+        /// <param name="input">A <see cref="Stream"/> from which any feedback from the server can be read.</param>
+        /// <param name="source">The content to upload.</param>
+        /// <param name="remoteFileName">The name of the remote file, without path, to which the content is uploaded.</param>
+        /// <remarks>
+        /// <paramref name="remoteFileName"/> is only used for raising the <see cref="Uploading"/> event.
+        /// </remarks>
+        private void UploadFileContent(IChannelSession channel, Stream input, Stream source, string remoteFileName)
+        {
+            var totalLength = source.Length;
             var buffer = new byte[BufferSize];
 
             var read = source.Read(buffer, 0, buffer.Length);
@@ -272,12 +305,12 @@ namespace Renci.SshNet
 
                 totalRead += read;
 
-                RaiseUploadingEvent(filename, length, totalRead);
+                RaiseUploadingEvent(remoteFileName, totalLength, totalRead);
 
                 read = source.Read(buffer, 0, buffer.Length);
             }
 
-            SendConfirmation(channel);
+            SendSuccessConfirmation(channel);
             CheckReturnCode(input);
         }
 
@@ -304,7 +337,7 @@ namespace Renci.SshNet
             RaiseDownloadingEvent(filename, length, length - needToRead);
 
             //  Send confirmation byte after last data byte was read
-            SendConfirmation(channel);
+            SendSuccessConfirmation(channel);
 
             CheckReturnCode(input);
         }
@@ -325,15 +358,15 @@ namespace Renci.SshNet
             }
         }
 
-        private static void SendConfirmation(IChannel channel)
+        private static void SendSuccessConfirmation(IChannel channel)
         {
-            SendData(channel, new byte[] { 0 });
+            SendData(channel, SuccessConfirmationCode);
         }
 
-        private void SendConfirmation(IChannel channel, byte errorCode, string message)
+        private void SendErrorConfirmation(IChannel channel, string message)
         {
-            SendData(channel, new[] { errorCode });
-            SendData(channel, string.Format("{0}\n", message));
+            SendData(channel, ErrorConfirmationCode);
+            SendData(channel, string.Concat(message, "\n"));
         }
 
         /// <summary>