'Telnet connection not writing tab to a file

I have a C# WPF application that is making a Telnet connection to write some content to a file on a Linux system from a Windows system. So say I have some text like this.

Here is a bit\tof text \r\n Followed by a new line.

Here is generally how the code goes:

string ipAddress = "11.11.11.11";
int port = 66;
TelnetConnection tc = new TelnetConnection(ipAddress, port);
string s = tc.Login("root", "password", 100);
string prompt = s.TrimEnd();
if (prompt != "$" && prompt != "#"){
    throw new Exception("Connection failed");
}
tc.WriteLine("echo $'Here is a bit\tof text \r\n Followed by a new line.' >> MyFile.txt");

Here is the TelnetConnection class I use:\

enum Verbs
{
    WILL = 251,
    WONT = 252,
    DO = 253,
    DONT = 254,
    IAC = 255
}

enum Options
{
    SGA = 3
}

class TelnetConnection
{
    TcpClient tcpSocket;

    int TimeOutMs = 10;

    public TelnetConnection(string Hostname, int Port)
    {
        tcpSocket = new TcpClient(Hostname, Port);

    }

    public string Login(string Username, string Password, int LoginTimeOutMs)
    {
        int oldTimeOutMs = TimeOutMs;
        TimeOutMs = LoginTimeOutMs;
        string s = Read();
        if (!s.TrimEnd().EndsWith(":"))
            throw new Exception("Failed to connect : no login prompt");
        WriteLine(Username);

        s += Read();
        if (!s.TrimEnd().EndsWith(":"))
            throw new Exception("Failed to connect : no password prompt");
        WriteLine(Password);

        s += Read();
        TimeOutMs = oldTimeOutMs;
        return s;
    }

    public void WriteLine(string cmd)
    {
        Write(cmd + "\n");
    }

    public void Write(string cmd)
    {
        if (!tcpSocket.Connected) return;
        byte[] buf = System.Text.ASCIIEncoding.ASCII.GetBytes(cmd.Replace("\0xFF", "\0xFF\0xFF"));
        tcpSocket.GetStream().Write(buf, 0, buf.Length);
        System.Threading.Thread.Sleep(1000);
    }

    public string Read()
    {
        if (!tcpSocket.Connected) return null;
        StringBuilder sb = new StringBuilder();
        do
        {
            ParseTelnet(sb);
            System.Threading.Thread.Sleep(TimeOutMs);
        } while (tcpSocket.Available > 0);
        return sb.ToString();
    }

    public bool IsConnected
    {
        get { return tcpSocket.Connected; }
    }

    void ParseTelnet(StringBuilder sb)
    {
        while (tcpSocket.Available > 0)
        {
            int input = tcpSocket.GetStream().ReadByte();
            switch (input)
            {
                case -1:
                    break;
                case (int)Verbs.IAC:
                    // interpret as command
                    int inputverb = tcpSocket.GetStream().ReadByte();
                    if (inputverb == -1) break;
                    switch (inputverb)
                    {
                        case (int)Verbs.IAC:
                            //literal IAC = 255 escaped, so append char 255 to string
                            sb.Append(inputverb);
                            break;
                        case (int)Verbs.DO:
                        case (int)Verbs.DONT:
                        case (int)Verbs.WILL:
                        case (int)Verbs.WONT:
                            // reply to all commands with "WONT", unless it is SGA (suppres go ahead)
                            int inputoption = tcpSocket.GetStream().ReadByte();
                            if (inputoption == -1) break;
                            tcpSocket.GetStream().WriteByte((byte)Verbs.IAC);
                            if (inputoption == (int)Options.SGA)
                                tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WILL : (byte)Verbs.DO);
                            else
                                tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WONT : (byte)Verbs.DONT);
                            tcpSocket.GetStream().WriteByte((byte)inputoption);
                            break;
                        default:
                            break;
                    }
                    break;
                default:
                    sb.Append((char)input);
                    break;
            }
        }
    }
}

When I go to check the file the it is written to like this

Here is a bitof text
Followed by a new line.

So the tab character isn't even read, but the new line is for some reason. Any ideas?



Solution 1:[1]

The correct answer is given by @Johannes Krackowizer but the explanation is missing a detail: the 'TelnetConnection' isn't a file. What you are doing is typing to the remote computer. @Greg M also mentions this.

When you send \t in your string, you are sending a literal <tab> character. The computer at the other end is trying to finish typing a file name for you because that's what happens when you hit <tab>. If you want to get a tab back you need to send a \ and a t. The t you can just send, but the \ needs to be sent as \\ because \ is an escape character, as you know. So send:

"echo $'Here is a bit\\tof text \r\n Followed by a new line.' >> MyFile.txt"

Note the bit\\tof with two backslashes. So why does \r\n work? Because it's <Enter> and <Ctrl>+<Enter>, and the string you are typing is being typed within single quotes. When you use single quotes you can hit <Enter> and keep typing until you are done and end with the final '.

As others have mentioned, printf is generally preferred:

"printf $'Here is a bit\\tof text \r\n Followed by a new line.\n' >> MyFile.txt"

Next, in your code, the backslash should also be escaped, so you probably need to escape those characters as well:

tc.WriteLine("printf $'Here is a bit\\\\tof text \\r\\n Followed by a new line.\\n' >> MyFile.txt");

Solution 2:[2]

It depends on what system you are connection to and what shell is running there but you probably need to double escape tab so it's not only \t but \\t so that a \ and a t is sent to the shell. But then you need to enable the echo command to interpret the \t not as string but as escaped thats done with the -e switch in bash. At least in bash that should look like this.

tc.WriteLine("echo -e \"Here is a bit\\tof text \\r\\n Followed by a new line.\" >> MyFile.txt");

Solution 3:[3]

This is actually very simple and is not telnet specific.

Open up Powershell or Cmd and try typing your string and you will see.

command lines interpret tab as either a command complete (auto-complete closest match ex. '>ec\t' will produce '>echo'), move to next field or throws the tab away.

you need to replace '\t' (for this particular use) with spaces.

Tabs CAN generally be sent over telnet. I do it all the time. usually the server interprets it as a request to move to next field.

For example 'MyName\tpassword' will fill in first field with MyName and then move to next field and fill with password

Solution 4:[4]

Use printf that would be more predictable

tc.WriteLine("printf 'Here is a bit\tof text \r\n Followed by a new line.\n'  >> MyFile.txt");

You can also use xxd (or similar) to verify the content

printf 'Here is a bit\tof text \r\n Followed by a new line.\n' | xxd
00000000: 4865 7265 2069 7320 6120 6269 7409 6f66  Here is a bit.of
00000010: 2074 6578 7420 0d0a 2046 6f6c 6c6f 7765   text .. Followe
00000020: 6420 6279 2061 206e 6577 206c 696e 652e  d by a new line.
00000030: 0a                                       .

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 rand'Chris
Solution 2 Johannes Krackowizer
Solution 3 Greg M
Solution 4 Diego Torres Milano