use std::fmt::{Display, Write};
use std::num::NonZeroU64;
use std::str;
use std::time::{SystemTime, UNIX_EPOCH};
use rand::prelude::*;
use crate::signature_method::{Plaintext, Sign, SignatureMethod};
use crate::util::*;
use crate::Credentials;
use super::Serializer;
#[derive(Clone, Debug)]
pub struct Authorizer<'a, SM: SignatureMethod> {
consumer_key: &'a str,
token: Option<&'a str>,
options: &'a Options<'a>,
authorization: String,
sign: SM::Sign,
append_delim_to_sign: bool,
#[cfg(debug_assertions)]
prev_key: String,
}
options! {
#[derive(Clone, Debug, Default)]
pub struct Options<'a> {
new;
callback: Option<&'a str>,
verifier: Option<&'a str>,
nonce: Option<&'a str>,
timestamp: Option<NonZeroU64>,
version: bool,
}
}
pub type PlaintextAuthorizer<'a> = Authorizer<'a, Plaintext>;
cfg_if::cfg_if! {
if #[cfg(feature = "hmac-sha1")] {
use crate::signature_method::HmacSha1;
pub type HmacSha1Authorizer<'a> = Authorizer<'a, HmacSha1>;
}
}
impl<'a, SM: SignatureMethod> Authorizer<'a, SM> {
pub fn new<T: Display>(
method: &str,
uri: T,
client: Credentials<&'a str>,
token: Option<Credentials<&'a str>>,
options: &'a Options<'a>,
) -> Self
where
SM: Default,
{
Self::with_signature_method(Default::default(), method, uri, client, token, options)
}
pub fn with_signature_method<T: Display>(
signature_method: SM,
method: &str,
uri: T,
client: Credentials<&'a str>,
token: Option<Credentials<&'a str>>,
options: &'a Options<'a>,
) -> Self {
let mut sign = signature_method.sign_with(client.secret, token.map(|t| t.secret));
let mut authorization = String::with_capacity(512);
authorization.push_str("OAuth ");
sign.request_method(method);
debug_assert!(
!uri.to_string().contains('?'),
"`uri` must not contain a query part",
);
sign.uri(PercentEncode(uri));
{
#[cfg(debug_assertions)]
{
Self {
consumer_key: client.identifier,
token: token.map(|t| t.identifier),
options,
authorization,
sign,
append_delim_to_sign: false,
prev_key: String::new(),
}
}
#[cfg(not(debug_assertions))]
{
Self {
consumer_key: client.identifier,
token: token.map(|t| t.identifier),
options,
authorization,
sign,
append_delim_to_sign: false,
}
}
}
}
}
impl<'a, SM: SignatureMethod> Authorizer<'a, SM> {
fn append_to_header_encoded<V: Display>(&mut self, k: &str, v: V) {
self.check_dictionary_order(k);
write!(self.authorization, r#"{}="{}","#, k, v).unwrap();
self.sign_delimiter();
}
fn sign_delimiter(&mut self) {
if self.append_delim_to_sign {
self.sign.delimiter();
} else {
self.append_delim_to_sign = true;
}
}
fn check_dictionary_order(&mut self, _k: &str) {
#[cfg(debug_assertions)]
{
assert!(
*self.prev_key <= *_k,
"appended key is less than previously appended one in dictionary order\
\n previous: `{:?}`,\
\n current: `{:?}`",
self.prev_key,
_k,
);
self.prev_key.clear();
self.prev_key.push_str(_k);
}
}
}
macro_rules! append_to_header {
(@inner $self:expr, $k:ident, $v:expr, $w:expr) => {{
let this = $self;
let k = concat!("oauth_", stringify!($k));
this.append_to_header_encoded(k, $v);
this.sign.$k($w);
}};
($self:expr, encoded $k:ident, $v:expr) => {{
let v = $v;
append_to_header!(@inner $self, $k, v, v);
}};
($self:expr, $k:ident, $v:expr) => {{
let v = $v;
append_to_header!(@inner $self, $k, percent_encode(v), DoublePercentEncode(v));
}};
}
impl<'a, SM: SignatureMethod> Serializer for Authorizer<'a, SM> {
type Output = String;
fn serialize_parameter<V: Display>(&mut self, k: &str, v: V) {
self.check_dictionary_order(k);
self.sign_delimiter();
self.sign.parameter(k, DoublePercentEncode(v));
}
fn serialize_parameter_encoded<V: Display>(&mut self, k: &str, v: V) {
self.check_dictionary_order(k);
self.sign_delimiter();
self.sign.parameter(k, PercentEncode(v));
}
fn serialize_oauth_callback(&mut self) {
if let Some(c) = self.options.callback {
append_to_header!(self, callback, c);
}
}
fn serialize_oauth_consumer_key(&mut self) {
append_to_header!(self, consumer_key, self.consumer_key);
}
fn serialize_oauth_nonce(&mut self) {
if self.sign.use_nonce() {
let mut nonce_buf;
if let Some(n) = self.options.nonce {
append_to_header!(self, nonce, n);
} else {
nonce_buf = Default::default();
append_to_header!(self, encoded nonce, gen_nonce(&mut nonce_buf));
}
}
}
fn serialize_oauth_signature_method(&mut self) {
let v = self.sign.get_signature_method_name();
self.append_to_header_encoded("oauth_signature_method", v);
self.sign.signature_method();
}
fn serialize_oauth_timestamp(&mut self) {
if self.sign.use_timestamp() {
let t = if let Some(t) = self.options.timestamp {
t.get()
} else {
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(d) => d.as_secs(),
#[cold]
Err(_) => 1,
}
};
append_to_header!(self, encoded timestamp, t);
}
}
fn serialize_oauth_token(&mut self) {
if let Some(t) = self.token {
append_to_header!(self, token, t);
}
}
fn serialize_oauth_verifier(&mut self) {
if let Some(v) = self.options.verifier {
append_to_header!(self, verifier, v);
}
}
fn serialize_oauth_version(&mut self) {
if self.options.version {
self.append_to_header_encoded("oauth_version", "1.0");
self.sign.version();
}
}
fn end(self) -> String {
let Self {
mut authorization,
sign,
..
} = self;
authorization.push_str("oauth_signature=");
write!(authorization, r#""{}""#, sign.end()).unwrap();
authorization
}
}
const NONCE_LEN: usize = 12;
fn gen_nonce(buf: &mut [u8; NONCE_LEN]) -> &str {
let mut rng = thread_rng();
let mut rand = [0u8; NONCE_LEN * 3 / 4];
rng.fill_bytes(&mut rand);
let i = rand.iter().position(|&b| b != 0).unwrap_or(rand.len());
let rand = &rand[i..];
let len = base64::encode_config_slice(&rand, base64::URL_SAFE_NO_PAD, buf);
let buf = &buf[..len];
debug_assert!(str::from_utf8(buf).is_ok(), "buf={:?}", buf);
unsafe { str::from_utf8_unchecked(buf) }
}