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
}
}
}