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
80
81
82
83
84
85
86
87
88
89
use async_std::fs::File;
#[cfg(feature = "unstable")]
use futures::io::Sink;
use std::fs;
use std::net::SocketAddr;
use std::path::Component;
use std::path::{Path, PathBuf};

use crate::error::{Error, Result};
use crate::packet;

/// Handler that serves read requests for a directory.
pub struct DirRoHandler {
    dir: PathBuf,
}

impl DirRoHandler {
    /// Create new handler for directory.
    pub fn new<P>(dir: P) -> Result<Self>
    where
        P: AsRef<Path>,
    {
        let dir = fs::canonicalize(dir.as_ref())?;

        if !dir.is_dir() {
            return Err(Error::NotDir(dir));
        }

        log!("TFTP directory: {}", dir.display());

        Ok(DirRoHandler {
            dir,
        })
    }
}

#[crate::async_trait]
impl crate::server::Handler for DirRoHandler {
    type Reader = File;
    #[cfg(feature = "unstable")]
    type Writer = Sink;

    async fn read_req_open(
        &mut self,
        _client: &SocketAddr,
        path: &Path,
    ) -> Result<(Self::Reader, Option<u64>), packet::Error> {
        // Strip some prefixes
        let path = path
            .strip_prefix("/")
            .or_else(|_| path.strip_prefix("./"))
            .unwrap_or(path);

        // Avoid directory traversal attack
        if path.components().any(|x| x == Component::ParentDir) {
            return Err(packet::Error::FileNotFound);
        }

        // Path should not start from root dir or have any Windows prefixes.
        // i.e. We accept only normal path components.
        match path.components().next() {
            Some(Component::Normal(_)) => {}
            _ => return Err(packet::Error::FileNotFound),
        }

        let path = self.dir.join(path);

        // Send only regular files
        if !path.is_file() {
            return Err(packet::Error::FileNotFound);
        }

        let file = File::open(&path).await?;
        let len = file.metadata().await.ok().map(|m| m.len());
        log!("TFTP sending file: {}", path.display());

        Ok((file, len))
    }

    #[cfg(feature = "unstable")]
    async fn write_open(
        &mut self,
        _client: &SocketAddr,
        _path: &Path,
        _size: Option<u64>,
    ) -> Result<Self::Writer, packet::Error> {
        Err(packet::Error::IllegalOperation)
    }
}