1use crate::{Error, STEAM_URL};
2
3#[cfg(feature = "reqwest-09x")]
4use futures::{
5 future::{self, Either},
6 Future, Stream,
7};
8
9#[derive(Debug, Clone)]
10pub struct Verifier {
28 claimed_id: u64,
29}
30
31impl Verifier {
32 pub fn from_querystring<S: AsRef<str>>(s: S) -> Result<(http::Request<Vec<u8>>, Self), Error> {
35 let form = serde_urlencoded::from_str(s.as_ref()).map_err(Error::Deserialize)?;
36
37 Self::from_parsed(form)
38 }
39
40 pub fn from_parsed(
45 mut login_data: SteamLoginData,
46 ) -> Result<(http::Request<Vec<u8>>, Self), Error> {
47 login_data.mode = "check_authentication".to_owned();
48
49 let verifier = {
50 let url = url::Url::parse(&login_data.claimed_id).map_err(|_| Error::ParseSteamId)?;
51 let mut segments = url.path_segments().ok_or(Error::ParseSteamId)?;
52 let id_segment = segments.next_back().ok_or(Error::ParseSteamId)?;
53
54 let claimed_id = id_segment.parse::<u64>().map_err(|_| Error::ParseSteamId)?;
55
56 Self { claimed_id }
57 };
58
59 let form_data = serde_urlencoded::to_string(login_data)
60 .map_err(Error::Serialize)?
61 .into_bytes();
62
63 let req = http::Request::builder()
64 .method(http::Method::POST)
65 .uri(STEAM_URL)
66 .header("Content-Type", "application/x-www-form-urlencoded")
67 .body(form_data)
68 .map_err(Error::BuildHttpStruct)?;
69
70 Ok((req, verifier))
71 }
72
73 pub fn verify_response<S: Into<String>>(self, response_body: S) -> Result<u64, Error> {
75 let is_valid = response_body
76 .into()
77 .split('\n')
78 .filter_map(|line| {
79 let mut pair = line.splitn(2, ':');
81 Some((pair.next()?, pair.next()?))
82 })
83 .any(|(k, v)| k == "is_valid" && v == "true");
84
85 if is_valid {
86 Ok(self.claimed_id)
87 } else {
88 Err(Error::AuthenticationFailed)
89 }
90 }
91
92 #[cfg(feature = "reqwest-09x")]
93 pub fn make_verify_request<S: AsRef<str>>(
96 client: &reqwest::Client,
97 querystring: S,
98 ) -> Result<u64, Error> {
99 let (req, verifier) = Self::from_querystring(querystring)?;
100
101 let (parts, body) = req.into_parts();
102
103 client
104 .post(&parts.uri.to_string())
105 .header("Content-Type", "application/x-www-form-urlencoded")
106 .body(body)
107 .send()
108 .map_err(Error::Reqwest)
109 .and_then(|mut response| {
110 let text = response.text().map_err(Error::Reqwest)?;
111
112 verifier.verify_response(text)
113 })
114 }
115
116 #[cfg(feature = "reqwest-09x")]
117 pub fn make_verify_request_async<S: AsRef<str>>(
120 client: &reqwest::r#async::Client,
121 querystring: S,
122 ) -> impl Future<Item = u64, Error = Error> {
123 let (req, verifier) = match Self::from_querystring(querystring) {
124 Ok(rv) => rv,
125 Err(e) => return Either::A(future::err(e)),
126 };
127
128 let (parts, body) = req.into_parts();
129
130 Either::B(
131 client
132 .post(&parts.uri.to_string())
133 .header("Content-Type", "application/x-www-form-urlencoded")
134 .body(body)
135 .send()
136 .map_err(Error::Reqwest)
137 .and_then(|res| res.into_body().concat2().map_err(Error::Reqwest))
138 .and_then(move |body| {
139 let s = std::str::from_utf8(&body)
140 .map_err(|_| Error::AuthenticationFailed)?
141 .to_owned();
142
143 verifier.verify_response(s)
144 }),
145 )
146 }
147}
148
149#[derive(Clone, Deserialize, Serialize, Debug)]
150pub struct SteamLoginData {
151 #[serde(rename = "openid.ns")]
152 ns: String,
153 #[serde(rename = "openid.mode")]
154 mode: String,
155 #[serde(rename = "openid.op_endpoint")]
156 op_endpoint: String,
157 #[serde(rename = "openid.claimed_id")]
158 claimed_id: String,
159 #[serde(rename = "openid.identity")]
160 identity: Option<String>,
161 #[serde(rename = "openid.return_to")]
162 return_to: String,
163 #[serde(rename = "openid.response_nonce")]
164 response_nonce: String,
165 #[serde(rename = "openid.invalidate_handle")]
166 invalidate_handle: Option<String>,
167 #[serde(rename = "openid.assoc_handle")]
168 assoc_handle: String,
169 #[serde(rename = "openid.signed")]
170 signed: String,
171 #[serde(rename = "openid.sig")]
172 sig: String,
173}