RemotePathShellQuoteTransformation.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. using System;
  2. using System.Text;
  3. using Renci.SshNet.Common;
  4. namespace Renci.SshNet
  5. {
  6. /// <summary>
  7. /// Quotes a path in a way to be suitable to be used with a shell-based server.
  8. /// </summary>
  9. internal sealed class RemotePathShellQuoteTransformation : IRemotePathTransformation
  10. {
  11. /// <summary>
  12. /// Quotes a path in a way to be suitable to be used with a shell-based server.
  13. /// </summary>
  14. /// <param name="path">The path to transform.</param>
  15. /// <returns>
  16. /// A quoted path.
  17. /// </returns>
  18. /// <exception cref="ArgumentNullException"><paramref name="path"/> is <see langword="null"/>.</exception>
  19. /// <remarks>
  20. /// <para>
  21. /// If <paramref name="path"/> contains a single-quote, that character is embedded
  22. /// in quotation marks (eg. "'"). Sequences of single-quotes are grouped in a single
  23. /// pair of quotation marks.
  24. /// </para>
  25. /// <para>
  26. /// An exclamation mark in <paramref name="path"/> is escaped with a backslash. This is
  27. /// necessary because C Shell interprets it as a meta-character for history substitution
  28. /// even when enclosed in single quotes or quotation marks.
  29. /// </para>
  30. /// <para>
  31. /// All other characters are enclosed in single quotes. Sequences of such characters are grouped
  32. /// in a single pair of single quotes.
  33. /// </para>
  34. /// <para>
  35. /// References:
  36. /// <list type="bullet">
  37. /// <item>
  38. /// <description><a href="http://pubs.opengroup.org/onlinepubs/7908799/xcu/chap2.html">Shell Command Language</a></description>
  39. /// </item>
  40. /// <item>
  41. /// <description><a href="https://earthsci.stanford.edu/computing/unix/shell/specialchars.php">Unix C-Shell special characters and their uses</a></description>
  42. /// </item>
  43. /// <item>
  44. /// <description><a href="https://docstore.mik.ua/orelly/unix3/upt/ch27_13.htm">Differences Between Bourne and C Shell Quoting</a></description>
  45. /// </item>
  46. /// </list>
  47. /// </para>
  48. /// </remarks>
  49. /// <example>
  50. /// <list type="table">
  51. /// <listheader>
  52. /// <term>Original</term>
  53. /// <term>Transformed</term>
  54. /// </listheader>
  55. /// <item>
  56. /// <term>/var/log/auth.log</term>
  57. /// <term>'/var/log/auth.log'</term>
  58. /// </item>
  59. /// <item>
  60. /// <term>/var/mp3/Guns N' Roses</term>
  61. /// <term>'/var/mp3/Guns N'"'"' Roses'</term>
  62. /// </item>
  63. /// <item>
  64. /// <term>/var/garbage!/temp</term>
  65. /// <term>'/var/garbage'\!'/temp'</term>
  66. /// </item>
  67. /// <item>
  68. /// <term>/var/would be 'kewl'!, not?</term>
  69. /// <term>'/var/would be '"'"'kewl'"'"\!', not?'</term>
  70. /// </item>
  71. /// <item>
  72. /// <term></term>
  73. /// <term>''</term>
  74. /// </item>
  75. /// <item>
  76. /// <term>Hello &quot;World&quot;</term>
  77. /// <term>'Hello "World"'</term>
  78. /// </item>
  79. /// </list>
  80. /// </example>
  81. public string Transform(string path)
  82. {
  83. ThrowHelper.ThrowIfNull(path);
  84. // result is at least value and (likely) leading/trailing single-quotes
  85. var sb = new StringBuilder(path.Length + 2);
  86. var state = ShellQuoteState.Unquoted;
  87. foreach (var c in path)
  88. {
  89. switch (c)
  90. {
  91. case '\'':
  92. // embed a single-quote in quotes
  93. switch (state)
  94. {
  95. case ShellQuoteState.Unquoted:
  96. // Start quoted string
  97. _ = sb.Append('"');
  98. break;
  99. case ShellQuoteState.Quoted:
  100. // Continue quoted string
  101. break;
  102. case ShellQuoteState.SingleQuoted:
  103. // Close single-quoted string
  104. _ = sb.Append('\'');
  105. // Start quoted string
  106. _ = sb.Append('"');
  107. break;
  108. default:
  109. break;
  110. }
  111. state = ShellQuoteState.Quoted;
  112. break;
  113. case '!':
  114. /*
  115. * In C-Shell, an exclamatation point can only be protected from shell interpretation
  116. * when escaped by a backslash.
  117. *
  118. * Source:
  119. * https://earthsci.stanford.edu/computing/unix/shell/specialchars.php
  120. */
  121. switch (state)
  122. {
  123. case ShellQuoteState.Unquoted:
  124. _ = sb.Append('\\');
  125. break;
  126. case ShellQuoteState.Quoted:
  127. // Close quoted string
  128. _ = sb.Append('"');
  129. _ = sb.Append('\\');
  130. break;
  131. case ShellQuoteState.SingleQuoted:
  132. // Close single quoted string
  133. _ = sb.Append('\'');
  134. _ = sb.Append('\\');
  135. break;
  136. default:
  137. break;
  138. }
  139. state = ShellQuoteState.Unquoted;
  140. break;
  141. default:
  142. switch (state)
  143. {
  144. case ShellQuoteState.Unquoted:
  145. // Start single-quoted string
  146. _ = sb.Append('\'');
  147. break;
  148. case ShellQuoteState.Quoted:
  149. // Close quoted string
  150. _ = sb.Append('"');
  151. // Start single-quoted string
  152. _ = sb.Append('\'');
  153. break;
  154. case ShellQuoteState.SingleQuoted:
  155. // Continue single-quoted string
  156. break;
  157. default:
  158. break;
  159. }
  160. state = ShellQuoteState.SingleQuoted;
  161. break;
  162. }
  163. _ = sb.Append(c);
  164. }
  165. switch (state)
  166. {
  167. case ShellQuoteState.Unquoted:
  168. break;
  169. case ShellQuoteState.Quoted:
  170. // Close quoted string
  171. _ = sb.Append('"');
  172. break;
  173. case ShellQuoteState.SingleQuoted:
  174. // Close single-quoted string
  175. _ = sb.Append('\'');
  176. break;
  177. default:
  178. break;
  179. }
  180. if (sb.Length == 0)
  181. {
  182. _ = sb.Append("''");
  183. }
  184. return sb.ToString();
  185. }
  186. private enum ShellQuoteState
  187. {
  188. Unquoted = 1,
  189. SingleQuoted = 2,
  190. Quoted = 3
  191. }
  192. }
  193. }