using System; using System.Text; namespace UMC.SshNet { /// /// Quotes a path in a way to be suitable to be used with a shell-based server. /// internal sealed class RemotePathShellQuoteTransformation : IRemotePathTransformation { /// /// Quotes a path in a way to be suitable to be used with a shell-based server. /// /// The path to transform. /// /// A quoted path. /// /// is null. /// /// /// If contains a single-quote, that character is embedded /// in quotation marks (eg. "'"). Sequences of single-quotes are grouped in a single /// pair of quotation marks. /// /// /// An exclamation mark in is escaped with a backslash. This is /// necessary because C Shell interprets it as a meta-character for history substitution /// even when enclosed in single quotes or quotation marks. /// /// /// All other characters are enclosed in single quotes. Sequences of such characters are grouped /// in a single pair of single quotes. /// /// /// References: /// /// /// Shell Command Language /// /// /// Unix C-Shell special characters and their uses /// /// /// Differences Between Bourne and C Shell Quoting /// /// /// /// /// /// /// /// Original /// Transformed /// /// /// /var/log/auth.log /// '/var/log/auth.log' /// /// /// /var/mp3/Guns N' Roses /// '/var/mp3/Guns N'"'"' Roses' /// /// /// /var/garbage!/temp /// '/var/garbage'\!'/temp' /// /// /// /var/would be 'kewl'!, not? /// '/var/would be '"'"'kewl'"'"\!', not?' /// /// /// /// '' /// /// /// Hello "World" /// 'Hello "World"' /// /// /// public string Transform(string path) { if (path is null) { throw new ArgumentNullException(nameof(path)); } // result is at least value and (likely) leading/trailing single-quotes var sb = new StringBuilder(path.Length + 2); var state = ShellQuoteState.Unquoted; foreach (var c in path) { switch (c) { case '\'': // embed a single-quote in quotes switch (state) { case ShellQuoteState.Unquoted: // Start quoted string _ = sb.Append('"'); break; case ShellQuoteState.Quoted: // Continue quoted string break; case ShellQuoteState.SingleQuoted: // Close single-quoted string _ = sb.Append('\''); // Start quoted string _ = sb.Append('"'); break; default: break; } state = ShellQuoteState.Quoted; break; case '!': /* * In C-Shell, an exclamatation point can only be protected from shell interpretation * when escaped by a backslash. * * Source: * https://earthsci.stanford.edu/computing/unix/shell/specialchars.php */ switch (state) { case ShellQuoteState.Unquoted: _ = sb.Append('\\'); break; case ShellQuoteState.Quoted: // Close quoted string _ = sb.Append('"'); _ = sb.Append('\\'); break; case ShellQuoteState.SingleQuoted: // Close single quoted string _ = sb.Append('\''); _ = sb.Append('\\'); break; default: break; } state = ShellQuoteState.Unquoted; break; default: switch (state) { case ShellQuoteState.Unquoted: // Start single-quoted string _ = sb.Append('\''); break; case ShellQuoteState.Quoted: // Close quoted string _ = sb.Append('"'); // Start single-quoted string _ = sb.Append('\''); break; case ShellQuoteState.SingleQuoted: // Continue single-quoted string break; default: break; } state = ShellQuoteState.SingleQuoted; break; } _ = sb.Append(c); } switch (state) { case ShellQuoteState.Unquoted: break; case ShellQuoteState.Quoted: // Close quoted string _ = sb.Append('"'); break; case ShellQuoteState.SingleQuoted: // Close single-quoted string _ = sb.Append('\''); break; default: break; } if (sb.Length == 0) { _ = sb.Append("''"); } return sb.ToString(); } private enum ShellQuoteState { Unquoted = 1, SingleQuoted = 2, Quoted = 3 } } }