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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
//! Types for working with HTTP authentication methods.

use crate::config::{proxy::Proxy, request::SetOpt};
use std::{
    fmt,
    ops::{BitOr, BitOrAssign},
};

/// Credentials consisting of a username and a secret (password) that can be
/// used to establish user identity.
#[derive(Clone)]
pub struct Credentials {
    username: String,
    password: String,
}

impl Credentials {
    /// Create credentials from a username and password.
    pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
        Self {
            username: username.into(),
            password: password.into(),
        }
    }
}

impl SetOpt for Credentials {
    fn set_opt<H>(&self, easy: &mut curl::easy::Easy2<H>) -> Result<(), curl::Error> {
        easy.username(&self.username)?;
        easy.password(&self.password)
    }
}

impl SetOpt for Proxy<Credentials> {
    fn set_opt<H>(&self, easy: &mut curl::easy::Easy2<H>) -> Result<(), curl::Error> {
        easy.proxy_username(&self.0.username)?;
        easy.proxy_password(&self.0.password)
    }
}

// Implement our own debug since we don't want to print passwords even on
// accident.
impl fmt::Debug for Credentials {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Credentials")
            .field("username", &self.username)
            .field("password", &"*****")
            .finish()
    }
}

/// Specifies one or more HTTP authentication schemes to use.
#[derive(Clone, Debug)]
pub struct Authentication(u8);

impl Default for Authentication {
    fn default() -> Self {
        Self::none()
    }
}

impl Authentication {
    /// Disable all authentication schemes. This is the default.
    pub const fn none() -> Self {
        Authentication(0)
    }

    /// Enable all available authentication schemes.
    pub const fn all() -> Self {
        #[allow(unused_mut)]
        let mut all = Self::basic().0 | Self::digest().0;

        #[cfg(feature = "spnego")]
        {
            all |= Self::negotiate().0;
        }

        Authentication(all)
    }

    /// HTTP Basic authentication.
    ///
    /// This authentication scheme sends the user name and password over the
    /// network in plain text. Avoid using this scheme without TLS as the
    /// credentials can be easily captured otherwise.
    pub const fn basic() -> Self {
        Authentication(0b0001)
    }

    /// HTTP Digest authentication.
    ///
    /// Digest authentication is defined in RFC 2617 and is a more secure way to
    /// do authentication over public networks than the regular old-fashioned
    /// Basic method.
    pub const fn digest() -> Self {
        Authentication(0b0010)
    }

    /// HTTP Negotiate (SPNEGO) authentication.
    ///
    /// Negotiate authentication is defined in RFC 4559 and is the most secure
    /// way to perform authentication over HTTP. Specifying [`Credentials`] is
    /// not necessary as credentials are provided by platform authentication
    /// means.
    ///
    /// You need to build libcurl with a suitable GSS-API library or SSPI on
    /// Windows for this to work. This is automatic when binding to curl
    /// statically, otherwise it depends on how your system curl is configured.
    ///
    /// # Availability
    ///
    /// This method is only available when the [`spnego`](../index.html#spnego)
    /// feature is enabled.
    #[cfg(feature = "spnego")]
    pub const fn negotiate() -> Self {
        Authentication(0b0100)
    }

    const fn contains(&self, other: Self) -> bool {
        (self.0 & other.0) == other.0
    }

    fn as_auth(&self) -> curl::easy::Auth {
        let mut auth = curl::easy::Auth::new();

        if self.contains(Authentication::basic()) {
            auth.basic(true);
        }

        if self.contains(Authentication::digest()) {
            auth.digest(true);
        }

        #[cfg(feature = "spnego")]
        {
            if self.contains(Authentication::negotiate()) {
                auth.gssnegotiate(true);
            }
        }

        auth
    }
}

impl BitOr for Authentication {
    type Output = Self;

    fn bitor(mut self, other: Self) -> Self {
        self |= other;
        self
    }
}

impl BitOrAssign for Authentication {
    fn bitor_assign(&mut self, rhs: Self) {
        self.0 |= rhs.0;
    }
}

impl SetOpt for Authentication {
    fn set_opt<H>(&self, easy: &mut curl::easy::Easy2<H>) -> Result<(), curl::Error> {
        #[cfg(feature = "spnego")]
        {
            if self.contains(Authentication::negotiate()) {
                // Ensure auth engine is enabled, even though credentials do not
                // need to be specified.
                easy.username("")?;
                easy.password("")?;
            }
        }

        easy.http_auth(&self.as_auth())
    }
}

impl SetOpt for Proxy<Authentication> {
    fn set_opt<H>(&self, easy: &mut curl::easy::Easy2<H>) -> Result<(), curl::Error> {
        #[cfg(feature = "spnego")]
        {
            if self.0.contains(Authentication::negotiate()) {
                // Ensure auth engine is enabled, even though credentials do not
                // need to be specified.
                easy.proxy_username("")?;
                easy.proxy_password("")?;
            }
        }

        easy.proxy_auth(&self.0.as_auth())
    }
}

#[cfg(test)]
mod tests {
    use super::Authentication;

    #[test]
    fn auth_default() {
        let auth = Authentication::default();

        assert!(!auth.contains(Authentication::basic()));
        assert!(!auth.contains(Authentication::digest()));
    }

    #[test]
    fn auth_all() {
        let auth = Authentication::all();

        assert!(auth.contains(Authentication::basic()));
        assert!(auth.contains(Authentication::digest()));
    }

    #[test]
    fn auth_single() {
        let auth = Authentication::basic();

        assert!(auth.contains(Authentication::basic()));
        assert!(!auth.contains(Authentication::digest()));

        let auth = Authentication::digest();

        assert!(!auth.contains(Authentication::basic()));
        assert!(auth.contains(Authentication::digest()));
    }
}