I'm building a telnet server in Rust. I want the server to be able to display text files and respond to custom single-character commands that are sent from the client. The user should be able to just press a key and have the server respond-- they shouldn't see their input, so I'm using the telnet ECHO option so that the client does not display the user's input. However, after I send the echo option, whenever I print a file or some string, extra whitespace is inserted.
I've condensed the code to isolate the problem:
file: main.rs
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:4321")?;
for stream in listener.incoming() {
let mut stream = stream?;
let contents = fs::read_to_string("file.txt")
.expect("Couldn't find file.");
// This array is [255, 251, 01]
stream.write(&[IAC.byte(), WILL.byte(), Echo.byte()])?;
stream.write(contents.as_bytes())?;
// This array is [255, 252, 01]
stream.write(&[IAC.byte(), WONT.byte(), Echo.byte()])?;
stream.write(contents.as_bytes())?;
}
Ok(())
}
file: file.txt
Hello
This
Is
A
File.
When I run this and connect locally via telnet:
celie:~$ telnet localhost 4321
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello
This
Is
A
File.
Hello
This
Is
A
File.
Connection closed by foreign host.
I can rearrange when I write the telnet codes to the stream, and I've used other telnet options and gotten valid responses back from the client, but as soon as I say "WILL Echo", all subsequent output gets all this extra whitespace, until I say "WONT Echo".
I want the file to simply display as normal with no whitespace added after newlines, which works when I don't use the echo option, even if I use other telnet options.
The whitespace doesn't seem like it should be related to the echo option at all! Any telnet insights are appreciated!
What you're doing is roughly the same as "raw mode" on a local terminal (which applications use to disable echo and line editing and handle input themselves) — in this situation, the server/application is responsible for sending two control characters at the end of the line, whereas ordinarily the OS kernel does this for you.
You must send a carriage return ("\r"
), which moves the cursor left, and a line feed ("\n"
), which moves the cursor down. You're sending only line feeds (as is conventional for modern text files), so the cursor moves down but not left. If you had sent a longer file, you would have noticed that text would wrap around the right edge but always start a new line below the end of the previous one.
As you copy the text to the TCP stream, you must check for line feeds and insert the necessary carriage returns.