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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use crate::{
    serde_str::{deserialize_from_str, serialize_to_str},
    Destination,
};
use distant_net::SecretKey32;
use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize};
use std::{
    convert::{TryFrom, TryInto},
    fmt, io,
    str::FromStr,
};
use uriparse::{URIReference, URI};

/// Represents credentials used for a distant server that is maintaining a single key
/// across all connections
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DistantSingleKeyCredentials {
    pub host: String,
    pub port: u16,
    pub key: SecretKey32,
    pub username: Option<String>,
}

impl fmt::Display for DistantSingleKeyCredentials {
    /// Converts credentials into string in the form of `distant://[username]:{key}@{host}:{port}`
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "distant://")?;
        if let Some(username) = self.username.as_ref() {
            write!(f, "{}", username)?;
        }
        write!(f, ":{}@{}:{}", self.key, self.host, self.port)
    }
}

impl FromStr for DistantSingleKeyCredentials {
    type Err = io::Error;

    /// Parse `distant://[username]:{key}@{host}` as credentials. Note that this requires the
    /// `distant` scheme to be included. If parsing without scheme is desired, call the
    /// [`DistantSingleKeyCredentials::try_from_uri_ref`] method instead with `require_scheme`
    /// set to false
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::try_from_uri_ref(s, true)
    }
}

impl Serialize for DistantSingleKeyCredentials {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serialize_to_str(self, serializer)
    }
}

impl<'de> Deserialize<'de> for DistantSingleKeyCredentials {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserialize_from_str(deserializer)
    }
}

impl DistantSingleKeyCredentials {
    /// Converts credentials into a [`Destination`] of the form `distant://[username]:{key}@{host}`,
    /// failing if the credentials would not produce a valid [`Destination`]
    pub fn try_to_destination(&self) -> io::Result<Destination> {
        let uri = self.try_to_uri()?;
        Destination::try_from(uri.as_uri_reference().to_borrowed())
            .map_err(|x| io::Error::new(io::ErrorKind::InvalidData, x))
    }

    /// Converts credentials into a [`URI`] of the form `distant://[username]:{key}@{host}`,
    /// failing if the credentials would not produce a valid [`URI`]
    pub fn try_to_uri(&self) -> io::Result<URI<'static>> {
        let uri_string = self.to_string();
        URI::try_from(uri_string.as_str())
            .map(URI::into_owned)
            .map_err(|x| io::Error::new(io::ErrorKind::InvalidData, x))
    }

    /// Parses credentials from a [`URIReference`], failing if the input was not a valid
    /// [`URIReference`] or if required parameters like `host` or `password` are missing or bad
    /// format
    ///
    /// If `require_scheme` is true, will enforce that a scheme is provided. Regardless, if a
    /// scheme is provided that is not `distant`, this will also fail
    pub fn try_from_uri_ref<'a, E>(
        uri_ref: impl TryInto<URIReference<'a>, Error = E>,
        require_scheme: bool,
    ) -> io::Result<Self>
    where
        E: std::error::Error + Send + Sync + 'static,
    {
        let uri_ref = uri_ref
            .try_into()
            .map_err(|x| io::Error::new(io::ErrorKind::Other, x))?;

        // Check if the scheme is correct, and if missing if we require it
        if let Some(scheme) = uri_ref.scheme() {
            if !scheme.as_str().eq_ignore_ascii_case("distant") {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "Scheme is not distant",
                ));
            }
        } else if require_scheme {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Missing scheme",
            ));
        }

        Ok(Self {
            host: uri_ref
                .host()
                .map(ToString::to_string)
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Missing host"))?,
            port: uri_ref
                .port()
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Missing port"))?,
            key: uri_ref
                .password()
                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Missing password"))
                .and_then(|x| {
                    x.parse()
                        .map_err(|x| io::Error::new(io::ErrorKind::InvalidInput, x))
                })?,
            username: uri_ref.username().map(ToString::to_string),
        })
    }
}