http_types/auth/
www_authenticate.rs1use crate::auth::AuthenticationScheme;
2use crate::bail_status as bail;
3use crate::headers::{HeaderName, HeaderValue, Headers, WWW_AUTHENTICATE};
4
5#[derive(Debug)]
40pub struct WwwAuthenticate {
41 scheme: AuthenticationScheme,
42 realm: String,
43}
44
45impl WwwAuthenticate {
46 pub fn new(scheme: AuthenticationScheme, realm: String) -> Self {
48 Self { scheme, realm }
49 }
50
51 pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
53 let headers = match headers.as_ref().get(WWW_AUTHENTICATE) {
54 Some(headers) => headers,
55 None => return Ok(None),
56 };
57
58 let value = headers.iter().last().unwrap();
61
62 let mut iter = value.as_str().splitn(2, ' ');
63 let scheme = iter.next();
64 let credential = iter.next();
65 let (scheme, realm) = match (scheme, credential) {
66 (None, _) => bail!(400, "Could not find scheme"),
67 (Some(_), None) => bail!(400, "Could not find realm"),
68 (Some(scheme), Some(realm)) => (scheme.parse()?, realm.to_owned()),
69 };
70
71 let realm = realm.trim_start();
72 let realm = match realm.strip_prefix(r#"realm=""#) {
73 Some(realm) => realm,
74 None => bail!(400, "realm not found"),
75 };
76
77 let mut chars = realm.chars();
78 let mut closing_quote = false;
79 let realm = (&mut chars)
80 .take_while(|c| {
81 if c == &'"' {
82 closing_quote = true;
83 false
84 } else {
85 true
86 }
87 })
88 .collect();
89 if !closing_quote {
90 bail!(400, r"Expected a closing quote");
91 }
92
93 Ok(Some(Self { scheme, realm }))
94 }
95
96 pub fn apply(&self, mut headers: impl AsMut<Headers>) {
98 headers.as_mut().insert(self.name(), self.value());
99 }
100
101 pub fn name(&self) -> HeaderName {
103 WWW_AUTHENTICATE
104 }
105
106 pub fn value(&self) -> HeaderValue {
108 let output = format!(r#"{} realm="{}", charset="UTF-8""#, self.scheme, self.realm);
109
110 unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
112 }
113
114 pub fn scheme(&self) -> AuthenticationScheme {
116 self.scheme
117 }
118
119 pub fn set_scheme(&mut self, scheme: AuthenticationScheme) {
121 self.scheme = scheme;
122 }
123
124 pub fn realm(&self) -> &str {
126 self.realm.as_str()
127 }
128
129 pub fn set_realm(&mut self, realm: String) {
131 self.realm = realm;
132 }
133}
134
135#[cfg(test)]
136mod test {
137 use super::*;
138 use crate::headers::Headers;
139
140 #[test]
141 fn smoke() -> crate::Result<()> {
142 let scheme = AuthenticationScheme::Basic;
143 let realm = "Access to the staging site";
144 let authz = WwwAuthenticate::new(scheme, realm.into());
145
146 let mut headers = Headers::new();
147 authz.apply(&mut headers);
148
149 assert_eq!(
150 headers["WWW-Authenticate"],
151 r#"Basic realm="Access to the staging site", charset="UTF-8""#
152 );
153
154 let authz = WwwAuthenticate::from_headers(headers)?.unwrap();
155
156 assert_eq!(authz.scheme(), AuthenticationScheme::Basic);
157 assert_eq!(authz.realm(), realm);
158 Ok(())
159 }
160
161 #[test]
162 fn bad_request_on_parse_error() {
163 let mut headers = Headers::new();
164 headers.insert(WWW_AUTHENTICATE, "<nori ate the tag. yum.>");
165 let err = WwwAuthenticate::from_headers(headers).unwrap_err();
166 assert_eq!(err.status(), 400);
167 }
168}