Skip to content

Commit

Permalink
Can't work with some special FTP response (#25)
Browse files Browse the repository at this point in the history
don't read response as ASCII
  • Loading branch information
veeso authored Dec 27, 2022
1 parent 030d564 commit 4c8c5b6
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 77 deletions.
88 changes: 55 additions & 33 deletions suppaftp/src/async_ftp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ impl FtpStream {

match ftp_stream.read_response(Status::Ready).await {
Ok(response) => {
debug!("Server READY; response: {}", response.body);
ftp_stream.welcome_msg = Some(response.body);
let welcome_msg = response.as_string().ok();
debug!("Server READY; response: {:?}", welcome_msg);
ftp_stream.welcome_msg = welcome_msg;
Ok(ftp_stream)
}
Err(err) => Err(err),
Expand All @@ -92,8 +93,9 @@ impl FtpStream {
debug!("Reading server response...");
match ftp_stream.read_response(Status::Ready).await {
Ok(response) => {
debug!("Server READY; response: {}", response.body);
ftp_stream.welcome_msg = Some(response.body);
let welcome_msg = response.as_string().ok();
debug!("Server READY; response: {:?}", welcome_msg);
ftp_stream.welcome_msg = welcome_msg;
Ok(ftp_stream)
}
Err(err) => Err(err),
Expand Down Expand Up @@ -209,8 +211,9 @@ impl FtpStream {
debug!("Reading server response...");
match stream.read_response(Status::Ready).await {
Ok(response) => {
debug!("Server READY; response: {}", response.body);
stream.welcome_msg = Some(response.body);
let welcome_msg = response.as_string().ok();
debug!("Server READY; response: {:?}", welcome_msg);
stream.welcome_msg = welcome_msg;
}
Err(err) => return Err(err),
}
Expand Down Expand Up @@ -300,14 +303,16 @@ impl FtpStream {
pub async fn pwd(&mut self) -> FtpResult<String> {
debug!("Getting working directory");
self.perform(Command::Pwd).await?;
self.read_response(Status::PathCreated)
.await
.and_then(
|Response { status, body }| match (body.find('"'), body.rfind('"')) {
(Some(begin), Some(end)) if begin < end => Ok(body[begin + 1..end].to_string()),
_ => Err(FtpError::UnexpectedResponse(Response::new(status, body))),
},
)
let response = self.read_response(Status::PathCreated).await?;
let body = response.as_string().map_err(|_| FtpError::BadResponse)?;
let status = response.status;
match (body.find('"'), body.rfind('"')) {
(Some(begin), Some(end)) if begin < end => Ok(body[begin + 1..end].to_string()),
_ => Err(FtpError::UnexpectedResponse(Response::new(
status,
response.body,
))),
}
}

/// This does nothing. This is usually just used to keep the connection open.
Expand Down Expand Up @@ -562,8 +567,9 @@ impl FtpStream {
self.perform(Command::Mdtm(pathname.as_ref().to_string()))
.await?;
let response: Response = self.read_response(Status::File).await?;
let body = response.as_string().map_err(|_| FtpError::BadResponse)?;

match MDTM_RE.captures(&response.body) {
match MDTM_RE.captures(&body) {
Some(caps) => {
let (year, month, day) = (
caps[1].parse::<i32>().unwrap(),
Expand All @@ -575,7 +581,10 @@ impl FtpStream {
caps[5].parse::<u32>().unwrap(),
caps[6].parse::<u32>().unwrap(),
);
Ok(Utc.ymd(year, month, day).and_hms(hour, minute, second))
Utc.with_ymd_and_hms(year, month, day, hour, minute, second)
.single()
.map(Ok)
.unwrap_or(Err(FtpError::BadResponse))
}
None => Err(FtpError::BadResponse),
}
Expand All @@ -587,8 +596,9 @@ impl FtpStream {
self.perform(Command::Size(pathname.as_ref().to_string()))
.await?;
let response: Response = self.read_response(Status::File).await?;
let body = response.as_string().map_err(|_| FtpError::BadResponse)?;

match SIZE_RE.captures(&response.body) {
match SIZE_RE.captures(&body) {
Some(caps) => Ok(caps[1].parse().unwrap()),
None => Err(FtpError::BadResponse),
}
Expand Down Expand Up @@ -639,8 +649,9 @@ impl FtpStream {
self.perform(Command::Pasv).await?;
// PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
let response: Response = self.read_response(Status::PassiveMode).await?;
let response_str = response.as_string().map_err(|_| FtpError::BadResponse)?;
let caps = PORT_RE
.captures(&response.body)
.captures(&response_str)
.ok_or_else(|| FtpError::UnexpectedResponse(response.clone()))?;
// If the regex matches we can be sure groups contains numbers
let (oct1, oct2, oct3, oct4) = (
Expand Down Expand Up @@ -749,34 +760,27 @@ impl FtpStream {

/// Retrieve single line response
pub async fn read_response_in(&mut self, expected_code: &[Status]) -> FtpResult<Response> {
let mut line = String::new();
self.reader
.read_line(&mut line)
.await
.map_err(FtpError::ConnectionError)?;
let mut line = Vec::new();
self.read_line(&mut line).await?;

trace!("CC IN: {}", line.trim_end());
trace!("CC IN: {:?}", line);

if line.len() < 5 {
return Err(FtpError::BadResponse);
}

let code: u32 = line[0..3].parse().map_err(|_| FtpError::BadResponse)?;
let code = Status::from(code);
let code_word: u32 = self.code_from_buffer(&line, 3)?;
let code = Status::from(code_word);

// multiple line reply
// loop while the line does not begin with the code and a space
let expected = format!("{} ", &line[0..3]);
let expected = [line[0], line[1], line[2], 0x20];
while line.len() < 5 || line[0..4] != expected {
line.clear();
if let Err(e) = self.reader.read_line(&mut line).await {
return Err(FtpError::ConnectionError(e));
}

trace!("CC IN: {}", line.trim_end());
self.read_line(&mut line).await?;
trace!("CC IN: {:?}", line);
}

line = String::from(line.trim());
let response: Response = Response::new(code, line);
// Return Ok or error with response
if expected_code.iter().any(|ec| code == *ec) {
Expand All @@ -786,6 +790,24 @@ impl FtpStream {
}
}

async fn read_line(&mut self, line: &mut Vec<u8>) -> FtpResult<usize> {
self.reader
.read_until(0x0A, line.as_mut())
.await
.map_err(FtpError::ConnectionError)?;
Ok(line.len())
}

/// Get code from buffer
fn code_from_buffer(&self, buf: &[u8], len: usize) -> Result<u32, FtpError> {
if buf.len() < len {
return Err(FtpError::BadResponse);
}
let buffer = buf[0..len].to_vec();
let as_string = String::from_utf8(buffer).map_err(|_| FtpError::BadResponse)?;
as_string.parse::<u32>().map_err(|_| FtpError::BadResponse)
}

/// Execute a command which returns list of strings in a separate stream
async fn stream_lines(&mut self, cmd: Command, open_code: Status) -> FtpResult<Vec<String>> {
let mut data_stream = BufReader::new(self.data_command(cmd).await?);
Expand Down
2 changes: 1 addition & 1 deletion suppaftp/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ impl File {
Ok(date) => {
// Case 2.
// Return NaiveDateTime from NaiveDate with time 00:00:00
date.and_hms(0, 0, 0)
date.and_hms_opt(0, 0, 0).unwrap()
}
Err(_) => {
// Might be case 1.
Expand Down
88 changes: 58 additions & 30 deletions suppaftp/src/sync_ftp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ use chrono::{DateTime, Utc};
use lazy_regex::{Lazy, Regex};
use std::io::{copy, BufRead, BufReader, Cursor, Read, Write};
use std::net::{Ipv4Addr, SocketAddr, TcpListener, TcpStream, ToSocketAddrs};
use std::string::String;

#[cfg(feature = "secure")]
pub use tls::TlsConnector;
Expand Down Expand Up @@ -66,8 +65,9 @@ impl FtpStream {
debug!("Reading server response...");
match ftp_stream.read_response(Status::Ready) {
Ok(response) => {
debug!("Server READY; response: {}", response.body);
ftp_stream.welcome_msg = Some(response.body);
let welcome_msg = response.as_string().ok();
debug!("Server READY; response: {:?}", welcome_msg);
ftp_stream.welcome_msg = welcome_msg;
Ok(ftp_stream)
}
Err(err) => Err(err),
Expand All @@ -94,8 +94,9 @@ impl FtpStream {
debug!("Reading server response...");
match ftp_stream.read_response(Status::Ready) {
Ok(response) => {
debug!("Server READY; response: {}", response.body);
ftp_stream.welcome_msg = Some(response.body);
let welcome_msg = response.as_string().ok();
debug!("Server READY; response: {:?}", welcome_msg);
ftp_stream.welcome_msg = welcome_msg;
Ok(ftp_stream)
}
Err(err) => Err(err),
Expand Down Expand Up @@ -218,8 +219,9 @@ impl FtpStream {
debug!("Reading server response...");
match stream.read_response(Status::Ready) {
Ok(response) => {
debug!("Server READY; response: {}", response.body);
stream.welcome_msg = Some(response.body);
let welcome_msg = response.as_string().ok();
debug!("Server READY; response: {:?}", welcome_msg);
stream.welcome_msg = welcome_msg;
}
Err(err) => return Err(err),
}
Expand Down Expand Up @@ -300,12 +302,17 @@ impl FtpStream {
debug!("Getting working directory");
self.perform(Command::Pwd)?;
self.read_response(Status::PathCreated)
.and_then(
|Response { status, body }| match (body.find('"'), body.rfind('"')) {
.and_then(|response| {
let body = response.as_string().map_err(|_| FtpError::BadResponse)?;
let status = response.status;
match (body.find('"'), body.rfind('"')) {
(Some(begin), Some(end)) if begin < end => Ok(body[begin + 1..end].to_string()),
_ => Err(FtpError::UnexpectedResponse(Response::new(status, body))),
},
)
_ => Err(FtpError::UnexpectedResponse(Response::new(
status,
response.body,
))),
}
})
}

/// This does nothing. This is usually just used to keep the connection open.
Expand Down Expand Up @@ -580,8 +587,9 @@ impl FtpStream {
debug!("Getting modification time for {}", pathname.as_ref());
self.perform(Command::Mdtm(pathname.as_ref().to_string()))?;
let response: Response = self.read_response(Status::File)?;
let body = response.as_string().map_err(|_| FtpError::BadResponse)?;

match MDTM_RE.captures(&response.body) {
match MDTM_RE.captures(&body) {
Some(caps) => {
let (year, month, day) = (
caps[1].parse::<i32>().unwrap(),
Expand All @@ -593,7 +601,11 @@ impl FtpStream {
caps[5].parse::<u32>().unwrap(),
caps[6].parse::<u32>().unwrap(),
);
Ok(Utc.ymd(year, month, day).and_hms(hour, minute, second))

Utc.with_ymd_and_hms(year, month, day, hour, minute, second)
.single()
.map(Ok)
.unwrap_or(Err(FtpError::BadResponse))
}
None => Err(FtpError::BadResponse),
}
Expand All @@ -604,8 +616,9 @@ impl FtpStream {
debug!("Getting file size for {}", pathname.as_ref());
self.perform(Command::Size(pathname.as_ref().to_string()))?;
let response: Response = self.read_response(Status::File)?;
let body = response.as_string().map_err(|_| FtpError::BadResponse)?;

match SIZE_RE.captures(&response.body) {
match SIZE_RE.captures(&body) {
Some(caps) => Ok(caps[1].parse().unwrap()),
None => Err(FtpError::BadResponse),
}
Expand Down Expand Up @@ -648,33 +661,29 @@ impl FtpStream {

/// Retrieve single line response
fn read_response_in(&mut self, expected_code: &[Status]) -> FtpResult<Response> {
let mut line = String::new();
self.reader
.read_line(&mut line)
.map_err(FtpError::ConnectionError)?;
let mut line = Vec::new();
self.read_line(&mut line)?;

trace!("CC IN: {}", line.trim_end());
trace!("CC IN: {:?}", line);

if line.len() < 5 {
return Err(FtpError::BadResponse);
}

let code: u32 = line[0..3].parse().map_err(|_| FtpError::BadResponse)?;
let code = Status::from(code);
let code_word: u32 = self.code_from_buffer(&line, 3)?;
let code = Status::from(code_word);

trace!("Code parsed from response: {} ({})", code, code_word);

// multiple line reply
// loop while the line does not begin with the code and a space
let expected = format!("{} ", &line[0..3]);
let expected = [line[0], line[1], line[2], 0x20];
while line.len() < 5 || line[0..4] != expected {
line.clear();
if let Err(e) = self.reader.read_line(&mut line) {
return Err(FtpError::ConnectionError(e));
}

trace!("CC IN: {}", line.trim_end());
self.read_line(&mut line)?;
trace!("CC IN: {:?}", line);
}

line = String::from(line.trim());
let response: Response = Response::new(code, line);
// Return Ok or error with response
if expected_code.iter().any(|ec| code == *ec) {
Expand All @@ -684,6 +693,24 @@ impl FtpStream {
}
}

/// Read bytes from reader until 0x0A or EOF is found
fn read_line(&mut self, line: &mut Vec<u8>) -> FtpResult<usize> {
self.reader
.read_until(0x0A, line.as_mut())
.map_err(FtpError::ConnectionError)?;
Ok(line.len())
}

/// Get code from buffer
fn code_from_buffer(&self, buf: &[u8], len: usize) -> Result<u32, FtpError> {
if buf.len() < len {
return Err(FtpError::BadResponse);
}
let buffer = buf[0..len].to_vec();
let as_string = String::from_utf8(buffer).map_err(|_| FtpError::BadResponse)?;
as_string.parse::<u32>().map_err(|_| FtpError::BadResponse)
}

/// Write data to stream with command to perform
fn perform(&mut self, command: Command) -> FtpResult<()> {
let command = command.to_string();
Expand Down Expand Up @@ -758,8 +785,9 @@ impl FtpStream {
self.perform(Command::Pasv)?;
// PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
let response: Response = self.read_response(Status::PassiveMode)?;
let response_str = response.as_string().map_err(|_| FtpError::BadResponse)?;
let caps = PORT_RE
.captures(&response.body)
.captures(&response_str)
.ok_or_else(|| FtpError::UnexpectedResponse(response.clone()))?;
// If the regex matches we can be sure groups contains numbers
let (oct1, oct2, oct3, oct4) = (
Expand Down
Loading

0 comments on commit 4c8c5b6

Please sign in to comment.