1use core::fmt::{self, Display, Write};
4use core::num::NonZeroU64;
5use core::str;
6
7use base64::{Engine as _};
8use rand::prelude::*;
9
10use crate::signature_method::{Sign, SignatureMethod};
11use crate::util::*;
12use crate::Credentials;
13
14use super::{Serializer, Urlencoder};
15
16cfg_type_param_hack! {
17 #[derive(Clone, Debug)]
23 pub struct Authorizer<
24 'a,
25 SM: SignatureMethod,
26 #[cfg(feature = "alloc")] W = alloc::string::String,
27 #[cfg(not(feature = "alloc"))] W,
28 > {
29 consumer_key: &'a str,
30 token: Option<&'a str>,
31 options: &'a Options<'a>,
32 data: Data<W>,
33 sign: SM::Sign,
34 append_delim_to_sign: bool,
35 #[cfg(all(feature = "alloc", debug_assertions))]
36 prev_key: alloc::string::String,
37 }
38}
39
40#[derive(Clone, Debug)]
41enum Data<W> {
42 Authorization(W),
43 Urlencode(Urlencoder<W>),
44}
45
46options! {
47 #[derive(Clone, Debug, Default)]
49 pub struct Options<'a> {
50 new;
52 callback: Option<&'a str>,
54 verifier: Option<&'a str>,
56 nonce: Option<&'a str>,
58 timestamp: Option<NonZeroU64>,
65 version: bool,
67 }
68}
69
70doc_auto_cfg! {
71 #[cfg(feature = "alloc")]
72 impl<'a, SM: SignatureMethod> Authorizer<'a, SM> {
73 pub fn authorization<T: Display>(
82 method: &str,
83 uri: T,
84 client: Credentials<&'a str>,
85 token: Option<Credentials<&'a str>>,
86 options: &'a Options<'a>,
87 signature_method: SM,
88 ) -> Self {
89 let buf = alloc::string::String::with_capacity(512);
90 Authorizer::authorization_with_buf(
91 buf,
92 method,
93 uri,
94 client,
95 token,
96 options,
97 signature_method,
98 )
99 }
100
101 pub fn form<T: Display>(
110 method: &str,
111 uri: T,
112 client: Credentials<&'a str>,
113 token: Option<Credentials<&'a str>>,
114 options: &'a Options<'a>,
115 signature_method: SM,
116 ) -> Self {
117 let buf = alloc::string::String::with_capacity(512);
118 Authorizer::form_with_buf(buf, method, uri, client, token, options, signature_method)
119 }
120 }
121}
122
123impl<'a, SM: SignatureMethod, W: Write> Authorizer<'a, SM, W> {
124 pub fn query(
133 method: &str,
134 uri: W,
135 client: Credentials<&'a str>,
136 token: Option<Credentials<&'a str>>,
137 options: &'a Options<'a>,
138 signature_method: SM,
139 ) -> Self
140 where
141 W: Display,
142 {
143 let sign = make_sign(method, &uri, client, token, signature_method);
144 let data = Data::Urlencode(Urlencoder::query(uri));
145 Authorizer::new_(data, sign, client, token, options)
146 }
147
148 pub fn authorization_with_buf<T: Display>(
151 mut buf: W,
152 method: &str,
153 uri: T,
154 client: Credentials<&'a str>,
155 token: Option<Credentials<&'a str>>,
156 options: &'a Options<'a>,
157 signature_method: SM,
158 ) -> Self {
159 buf.write_str("OAuth ").unwrap();
160 let data = Data::Authorization(buf);
161 let sign = make_sign(method, uri, client, token, signature_method);
162 Authorizer::new_(data, sign, client, token, options)
163 }
164
165 pub fn form_with_buf<T: Display>(
167 buf: W,
168 method: &str,
169 uri: T,
170 client: Credentials<&'a str>,
171 token: Option<Credentials<&'a str>>,
172 options: &'a Options<'a>,
173 signature_method: SM,
174 ) -> Self {
175 let data = Data::Urlencode(Urlencoder::form_with_buf(buf));
176 let sign = make_sign(method, uri, client, token, signature_method);
177 Authorizer::new_(data, sign, client, token, options)
178 }
179
180 fn new_(
181 data: Data<W>,
182 sign: SM::Sign,
183 client: Credentials<&'a str>,
184 token: Option<Credentials<&'a str>>,
185 options: &'a Options<'a>,
186 ) -> Self {
187 cfg_if::cfg_if! {
188 if #[cfg(all(feature = "alloc", debug_assertions))] {
189 Authorizer {
190 consumer_key: client.identifier,
191 token: token.map(|t| t.identifier),
192 options,
193 data,
194 sign,
195 append_delim_to_sign: false,
196 prev_key: alloc::string::String::new(),
197 }
198 } else {
199 Authorizer {
200 consumer_key: client.identifier,
201 token: token.map(|t| t.identifier),
202 options,
203 data,
204 sign,
205 append_delim_to_sign: false,
206 }
207 }
208 }
209 }
210}
211
212fn make_sign<SM: SignatureMethod, T: Display>(
213 method: &str,
214 uri: T,
215 client: Credentials<&str>,
216 token: Option<Credentials<&str>>,
217 signature_method: SM,
218) -> SM::Sign {
219 #[cfg(debug_assertions)]
224 {
225 struct AssertNotContainQuestion;
226 impl Write for AssertNotContainQuestion {
227 #[track_caller]
228 fn write_str(&mut self, uri: &str) -> fmt::Result {
229 assert!(!uri.contains('?'), "`uri` must not contain a query part");
230 Ok(())
231 }
232 }
233 write!(AssertNotContainQuestion, "{}", uri).unwrap();
234 }
235
236 let mut ret = signature_method.sign_with(client.secret, token.map(|t| t.secret));
237 ret.request_method(method);
238 ret.uri(PercentEncode(uri));
239
240 ret
241}
242
243impl<'a, SM: SignatureMethod, W: Write> Authorizer<'a, SM, W> {
244 fn append_to_header_encoded<V: Display>(&mut self, k: &str, v: V) {
245 self.check_dictionary_order(k);
246 match self.data {
247 Data::Authorization(ref mut header) => write!(header, r#"{}="{}","#, k, v).unwrap(),
248 Data::Urlencode(ref mut encoder) => encoder.serialize_parameter_encoded(k, v),
249 }
250 self.sign_delimiter();
251 }
252
253 fn sign_delimiter(&mut self) {
254 if self.append_delim_to_sign {
255 self.sign.delimiter();
256 } else {
257 self.append_delim_to_sign = true;
258 }
259 }
260
261 fn check_dictionary_order(&mut self, _k: &str) {
262 #[cfg(all(feature = "alloc", debug_assertions))]
263 {
264 assert!(
265 *self.prev_key <= *_k,
266 "appended key is less than previously appended one in dictionary order\
267 \n previous: `{:?}`,\
268 \n current: `{:?}`",
269 self.prev_key,
270 _k,
271 );
272 self.prev_key.clear();
273 self.prev_key.push_str(_k);
274 }
275 }
276}
277
278macro_rules! append_to_header {
279 (@inner $self:expr, $k:ident, $v:expr, $w:expr) => {{
280 let this = $self;
281 let k = concat!("oauth_", stringify!($k));
282 this.append_to_header_encoded(k, $v);
283 this.sign.$k($w);
284 }};
285 ($self:expr, encoded $k:ident, $v:expr) => {{
286 let v = $v;
287 append_to_header!(@inner $self, $k, v, v);
288 }};
289 ($self:expr, $k:ident, $v:expr) => {{
290 let v = $v;
291 append_to_header!(@inner $self, $k, percent_encode(v), DoublePercentEncode(v));
292 }};
293}
294
295impl<'a, SM: SignatureMethod, W: Write> Serializer for Authorizer<'a, SM, W> {
296 type Output = W;
297
298 fn serialize_parameter<V: Display>(&mut self, key: &str, value: V) {
299 self.check_dictionary_order(key);
300 self.sign_delimiter();
301 self.sign.parameter(key, DoublePercentEncode(value));
302 }
303
304 fn serialize_parameter_encoded<V: Display>(&mut self, key: &str, value: V) {
305 self.check_dictionary_order(key);
306 self.sign_delimiter();
307 self.sign.parameter(key, PercentEncode(value));
308 }
309
310 fn serialize_oauth_callback(&mut self) {
311 if let Some(c) = self.options.callback {
312 append_to_header!(self, callback, c);
313 }
314 }
315
316 fn serialize_oauth_consumer_key(&mut self) {
317 append_to_header!(self, consumer_key, self.consumer_key);
318 }
319
320 fn serialize_oauth_nonce(&mut self) {
321 if self.sign.use_nonce() {
322 if let Some(n) = self.options.nonce {
323 append_to_header!(self, nonce, n);
324 } else {
325 let mut nonce_buf = Default::default();
326 append_to_header!(self, encoded nonce, gen_nonce(&mut nonce_buf, &mut get_rng()));
327 }
328 }
329 }
330
331 fn serialize_oauth_signature_method(&mut self) {
332 let v = self.sign.get_signature_method_name();
333 self.append_to_header_encoded("oauth_signature_method", v);
334 self.sign.signature_method();
335 }
336
337 fn serialize_oauth_timestamp(&mut self) {
338 if self.sign.use_timestamp() {
339 let t = if let Some(t) = self.options.timestamp {
340 t.get()
341 } else {
342 get_current_timestamp()
343 };
344 append_to_header!(self, encoded timestamp, t);
345 }
346 }
347
348 fn serialize_oauth_token(&mut self) {
349 if let Some(t) = self.token {
350 append_to_header!(self, token, t);
351 }
352 }
353
354 fn serialize_oauth_verifier(&mut self) {
355 if let Some(v) = self.options.verifier {
356 append_to_header!(self, verifier, v);
357 }
358 }
359
360 fn serialize_oauth_version(&mut self) {
361 if self.options.version {
362 self.append_to_header_encoded("oauth_version", "1.0");
363 self.sign.version();
364 }
365 }
366
367 fn end(self) -> W {
368 let Self { data, sign, .. } = self;
369
370 match data {
371 Data::Authorization(mut header) => {
372 header.write_str("oauth_signature=").unwrap();
373 write!(header, r#""{}""#, sign.end()).unwrap();
374 header
375 }
376 Data::Urlencode(mut encoder) => {
377 encoder.serialize_parameter_encoded("oauth_signature", sign.end());
378 encoder.end()
379 }
380 }
381 }
382}
383
384fn get_current_timestamp() -> u64 {
385 cfg_if::cfg_if! {
386 if #[cfg(all(feature = "js", target_arch = "wasm32", target_os = "unknown"))] {
388 (js_sys::Date::now() / 1000.0) as u64
389 } else if #[cfg(feature = "std")] {
390 use std::time::{SystemTime, UNIX_EPOCH};
391 match SystemTime::now().duration_since(UNIX_EPOCH) {
392 Ok(d) => d.as_secs(),
393 Err(_) => 1,
394 }
395 } else {
396 panic!(
397 "Attempted to get current timestamp in `no_std` mode. You must either use a \
398 signature method that do not use timestamp (i.e. SignatureMethod::Sign::timestamp` \
399 returns `true`) or explicitly set the timestamp via `Builder::timestamp` or \
400 `serializer::auth::Options::timestamp`",
401 );
402 }
403 }
404}
405
406fn get_rng() -> impl RngCore + CryptoRng {
407 cfg_if::cfg_if! {
408 if #[cfg(feature = "std")] {
409 thread_rng()
410 } else {
411 rand::rngs::OsRng
412 }
413 }
414}
415
416const NONCE_LEN: usize = 12;
426
427fn gen_nonce<'a, R: RngCore + CryptoRng>(buf: &'a mut [u8; NONCE_LEN], rng: &mut R) -> &'a str {
428 let mut rand = [0_u8; NONCE_LEN * 3 / 4];
429 rng.fill_bytes(&mut rand);
430
431 let i = rand.iter().position(|&b| b != 0).unwrap_or(rand.len());
433 let rand = &rand[i..];
434
435 let len = base64::engine::general_purpose::URL_SAFE_NO_PAD
436 .encode_slice(rand, buf)
437 .unwrap();
438 let buf = &buf[..len];
439
440 str::from_utf8(buf).unwrap()
441}