1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
use super::{Reply, command::Command, error::ControlChanError, line_parser};
use bytes::BytesMut;
use std::io::Write;
use tokio_util::codec::{Decoder, Encoder};
// FTPCodec implements tokio's `Decoder` and `Encoder` traits for the control channel, that we'll
// use to decode FTP commands and encode their responses.
pub struct FtpCodec {
// Stored index of the next index to examine for a '\n' character. This is used to optimize
// searching. For example, if `decode` was called with `abc`, it would hold `3`, because that
// is the next index to examine. The next time `decode` is called with `abcde\n`, we will only
// look at `de\n` before returning.
next_index: usize,
}
impl FtpCodec {
pub fn new() -> Self {
FtpCodec { next_index: 0 }
}
}
impl Decoder for FtpCodec {
type Item = Command;
type Error = ControlChanError;
// Here we decode the incoming bytes into a meaningful command. We'll split on newlines, and
// parse the resulting line using `Command::parse()`. This method will be called by tokio.
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Command>, Self::Error> {
if let Some(newline_offset) = buf[self.next_index..].iter().position(|b| *b == b'\n') {
let newline_index = newline_offset + self.next_index;
let line = buf.split_to(newline_index + 1);
self.next_index = 0;
Ok(Some(line_parser::parse(line)?))
} else {
self.next_index = buf.len();
Ok(None)
}
}
}
impl Encoder<Reply> for FtpCodec {
type Error = ControlChanError;
// Here we encode the outgoing response
fn encode(&mut self, reply: Reply, buf: &mut BytesMut) -> Result<(), Self::Error> {
let mut buffer = vec![];
match reply {
Reply::None => {
return Ok(());
}
Reply::CodeAndMsg { code, msg } => {
if msg.is_empty() {
writeln!(buffer, "{}\r", code as u32)?;
} else {
writeln!(buffer, "{} {}\r", code as u32, msg)?;
}
}
Reply::MultiLine { code, mut lines } => {
// Get the last line since it needs to be preceded by the response code.
let last_line = lines.pop().unwrap_or_default();
// Lines starting with a digit should be indented
for it in lines.iter_mut() {
if it.chars().next().unwrap().is_ascii_digit() {
it.insert(0, ' ');
}
}
if lines.is_empty() {
writeln!(buffer, "{} {}\r", code as u32, last_line)?;
} else {
write!(buffer, "{}-{}\r\n{} {}\r\n", code as u32, lines.join("\r\n"), code as u32, last_line)?;
}
}
}
buf.extend(&buffer);
Ok(())
}
}