1use std::{
4 fmt::{self, Display, Formatter},
5 str::FromStr,
6};
7
8use base64::{Engine, display::Base64Display, prelude::BASE64_STANDARD};
9use ttpkit::header::HeaderFieldValue;
10
11use crate::Error;
12
13#[derive(Clone)]
15pub struct BasicAuth {
16 username: String,
17 password: String,
18}
19
20impl BasicAuth {
21 pub fn new<U, P>(username: U, password: P) -> Self
23 where
24 U: Into<String>,
25 P: Into<String>,
26 {
27 Self {
28 username: username.into(),
29 password: password.into(),
30 }
31 }
32
33 #[inline]
35 pub fn username(&self) -> &str {
36 &self.username
37 }
38
39 #[inline]
41 pub fn password(&self) -> &str {
42 &self.password
43 }
44}
45
46impl Display for BasicAuth {
47 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
48 let credentials = format!("{}:{}", self.username, self.password);
49
50 let b64c = Base64Display::new(credentials.as_bytes(), &BASE64_STANDARD);
51
52 write!(f, "Basic {b64c}")
53 }
54}
55
56impl From<BasicAuth> for HeaderFieldValue {
57 fn from(auth: BasicAuth) -> Self {
58 HeaderFieldValue::from(auth.to_string())
59 }
60}
61
62impl FromStr for BasicAuth {
63 type Err = Error;
64
65 fn from_str(s: &str) -> Result<Self, Self::Err> {
66 let (scheme, rest) = s
67 .trim_ascii_start()
68 .split_once(|c| char::is_ascii_whitespace(&c))
69 .ok_or_else(|| Error::from_static_msg("invalid basic authorization header"))?;
70
71 if !scheme.eq_ignore_ascii_case("basic") {
72 return Err(Error::from_static_msg("unexpected authorization scheme"));
73 }
74
75 let credentials = BASE64_STANDARD
76 .decode(rest.trim_ascii())
77 .map_err(|_| Error::from_static_msg("invalid basic authorization credentials"))?;
78
79 let (username, password) = str::from_utf8(&credentials)
80 .ok()
81 .and_then(|credentials| credentials.split_once(':'))
82 .ok_or_else(|| Error::from_static_msg("invalid basic authorization credentials"))?;
83
84 let res = Self {
85 username: username.into(),
86 password: password.into(),
87 };
88
89 Ok(res)
90 }
91}