Created
February 6, 2022 09:35
-
-
Save Tosainu/006267e5da4a3f361bbafa6ef58940db to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This file is automatically @generated by Cargo. | |
# It is not intended for manual editing. | |
version = 3 | |
[[package]] | |
name = "autocfg" | |
version = "1.0.1" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" | |
[[package]] | |
name = "bitflags" | |
version = "1.3.2" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" | |
[[package]] | |
name = "cc" | |
version = "1.0.72" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" | |
[[package]] | |
name = "cfg-if" | |
version = "1.0.0" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | |
[[package]] | |
name = "curl" | |
version = "0.4.42" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "7de97b894edd5b5bcceef8b78d7da9b75b1d2f2f9a910569d0bde3dd31d84939" | |
dependencies = [ | |
"curl-sys", | |
"libc", | |
"openssl-probe", | |
"openssl-sys", | |
"schannel", | |
"socket2", | |
"winapi", | |
] | |
[[package]] | |
name = "curl-sys" | |
version = "0.4.52+curl-7.81.0" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "14b8c2d1023ea5fded5b7b892e4b8e95f70038a421126a056761a84246a28971" | |
dependencies = [ | |
"cc", | |
"libc", | |
"libz-sys", | |
"openssl-sys", | |
"pkg-config", | |
"vcpkg", | |
"winapi", | |
] | |
[[package]] | |
name = "dirs" | |
version = "4.0.0" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" | |
dependencies = [ | |
"dirs-sys", | |
] | |
[[package]] | |
name = "dirs-sys" | |
version = "0.3.6" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" | |
dependencies = [ | |
"libc", | |
"redox_users", | |
"winapi", | |
] | |
[[package]] | |
name = "foreign-types" | |
version = "0.3.2" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" | |
dependencies = [ | |
"foreign-types-shared", | |
] | |
[[package]] | |
name = "foreign-types-shared" | |
version = "0.1.1" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" | |
[[package]] | |
name = "getrandom" | |
version = "0.2.4" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" | |
dependencies = [ | |
"cfg-if", | |
"libc", | |
"wasi", | |
] | |
[[package]] | |
name = "lazy_static" | |
version = "1.4.0" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" | |
[[package]] | |
name = "libc" | |
version = "0.2.116" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" | |
[[package]] | |
name = "libz-sys" | |
version = "1.1.3" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" | |
dependencies = [ | |
"cc", | |
"libc", | |
"pkg-config", | |
"vcpkg", | |
] | |
[[package]] | |
name = "once_cell" | |
version = "1.9.0" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" | |
[[package]] | |
name = "openssl" | |
version = "0.10.38" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" | |
dependencies = [ | |
"bitflags", | |
"cfg-if", | |
"foreign-types", | |
"libc", | |
"once_cell", | |
"openssl-sys", | |
] | |
[[package]] | |
name = "openssl-probe" | |
version = "0.1.5" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" | |
[[package]] | |
name = "openssl-src" | |
version = "111.17.0+1.1.1m" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "05d6a336abd10814198f66e2a91ccd7336611f30334119ca8ce300536666fcf4" | |
dependencies = [ | |
"cc", | |
] | |
[[package]] | |
name = "openssl-sys" | |
version = "0.9.72" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" | |
dependencies = [ | |
"autocfg", | |
"cc", | |
"libc", | |
"openssl-src", | |
"pkg-config", | |
"vcpkg", | |
] | |
[[package]] | |
name = "pkg-config" | |
version = "0.3.24" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" | |
[[package]] | |
name = "ppv-lite86" | |
version = "0.2.16" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" | |
[[package]] | |
name = "rand" | |
version = "0.8.4" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" | |
dependencies = [ | |
"libc", | |
"rand_chacha", | |
"rand_core", | |
"rand_hc", | |
] | |
[[package]] | |
name = "rand_chacha" | |
version = "0.3.1" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" | |
dependencies = [ | |
"ppv-lite86", | |
"rand_core", | |
] | |
[[package]] | |
name = "rand_core" | |
version = "0.6.3" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" | |
dependencies = [ | |
"getrandom", | |
] | |
[[package]] | |
name = "rand_hc" | |
version = "0.3.1" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" | |
dependencies = [ | |
"rand_core", | |
] | |
[[package]] | |
name = "redox_syscall" | |
version = "0.2.10" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" | |
dependencies = [ | |
"bitflags", | |
] | |
[[package]] | |
name = "redox_users" | |
version = "0.4.0" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" | |
dependencies = [ | |
"getrandom", | |
"redox_syscall", | |
] | |
[[package]] | |
name = "schannel" | |
version = "0.1.19" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" | |
dependencies = [ | |
"lazy_static", | |
"winapi", | |
] | |
[[package]] | |
name = "socket2" | |
version = "0.4.4" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" | |
dependencies = [ | |
"libc", | |
"winapi", | |
] | |
[[package]] | |
name = "t" | |
version = "0.1.0" | |
dependencies = [ | |
"curl", | |
"dirs", | |
"openssl", | |
"rand", | |
] | |
[[package]] | |
name = "vcpkg" | |
version = "0.2.15" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" | |
[[package]] | |
name = "wasi" | |
version = "0.10.2+wasi-snapshot-preview1" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" | |
[[package]] | |
name = "winapi" | |
version = "0.3.9" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" | |
dependencies = [ | |
"winapi-i686-pc-windows-gnu", | |
"winapi-x86_64-pc-windows-gnu", | |
] | |
[[package]] | |
name = "winapi-i686-pc-windows-gnu" | |
version = "0.4.0" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" | |
[[package]] | |
name = "winapi-x86_64-pc-windows-gnu" | |
version = "0.4.0" | |
source = "registry+https://github.com/rust-lang/crates.io-index" | |
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "t" | |
version = "0.1.0" | |
edition = "2021" | |
[[bin]] | |
name = "t" | |
path = "main.rs" | |
[dependencies] | |
curl = "0.4.42" | |
dirs = "4.0.0" | |
openssl = { version = "0.10.38", features = ["vendored"] } | |
rand = "0.8.4" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::io::{BufRead, Read}; | |
fn main() -> t::Result<()> { | |
let [consumer_key, consumer_secret, access_token, access_secret] = { | |
let cfg_file = dirs::config_dir() | |
.ok_or_else(|| t::Error::String("$HOME or $XDG_CONFIG_FIR may not set".to_owned()))? | |
.join("t") | |
.join("config"); | |
let cfg = std::fs::OpenOptions::new().read(true).open(cfg_file)?; | |
let mut cfg = std::io::BufReader::new(cfg); | |
let mut lines = [String::new(), String::new(), String::new(), String::new()]; | |
for line in &mut lines { | |
cfg.read_line(line)?; | |
} | |
lines | |
}; | |
let oauth = t::Oauth::new( | |
consumer_key.trim_end(), | |
consumer_secret.trim_end(), | |
access_token.trim_end(), | |
access_secret.trim_end(), | |
); | |
let tweet = if let Some(tweet) = std::env::args().nth(1) { | |
tweet | |
} else { | |
let mut tweet = String::new(); | |
std::io::stdin().read_to_string(&mut tweet)?; | |
tweet | |
}; | |
t::post( | |
"https://api.twitter.com/1.1/statuses/update.json", | |
&oauth, | |
&[("status", &tweet)], | |
) | |
} | |
mod t { | |
use std::collections::BTreeMap; | |
#[derive(Debug)] | |
pub struct Oauth<'a> { | |
consumer_key: &'a str, | |
consumer_secret: &'a str, | |
access_token: &'a str, | |
access_secret: &'a str, | |
} | |
impl<'a> Oauth<'a> { | |
pub fn new( | |
consumer_key: &'a str, | |
consumer_secret: &'a str, | |
access_token: &'a str, | |
access_secret: &'a str, | |
) -> Oauth<'a> { | |
Oauth { | |
consumer_key, | |
consumer_secret, | |
access_token, | |
access_secret, | |
} | |
} | |
} | |
#[derive(Clone, Copy, PartialEq)] | |
enum RequestType { | |
Get, | |
Post, | |
} | |
#[allow(dead_code)] | |
pub fn get(url: &str, oauth: &Oauth, params: &[(&str, &str)]) -> Result<()> { | |
req(RequestType::Get, url, oauth, params) | |
} | |
#[allow(dead_code)] | |
pub fn post(url: &str, oauth: &Oauth, params: &[(&str, &str)]) -> Result<()> { | |
req(RequestType::Post, url, oauth, params) | |
} | |
fn req(reqtype: RequestType, url: &str, oauth: &Oauth, params: &[(&str, &str)]) -> Result<()> { | |
let auth_header = oauth_header(reqtype, url, oauth, params)?; | |
let mut header = curl::easy::List::new(); | |
header.append(&auth_header)?; | |
let query = query_str(&BTreeMap::from_iter(params.iter().cloned())); | |
let mut c = curl::easy::Easy::new(); | |
if query.is_empty() { | |
c.url(url)?; | |
} else { | |
let url = format!("{}?{}", url, query); | |
c.url(&url)?; | |
} | |
match reqtype { | |
RequestType::Get => c.get(true)?, | |
RequestType::Post => c.post(true)?, | |
} | |
c.http_headers(header)?; | |
c.write_function(|data| { | |
use std::io::Write; | |
std::io::stdout().write_all(data).unwrap(); | |
Ok(data.len()) | |
})?; | |
c.perform()?; | |
Ok(()) | |
} | |
fn oauth_header( | |
reqtype: RequestType, | |
url: &str, | |
oauth: &Oauth, | |
params: &[(&str, &str)], | |
) -> Result<String> { | |
let mut auth_params = auth_params(oauth); | |
for (k, v) in params.iter() { | |
auth_params.insert(k.to_string(), v.to_string()); | |
} | |
let signature_key = PercentEncoder::new(oauth.consumer_secret.as_bytes().iter()) | |
.chain(std::iter::once('&')) | |
.chain(PercentEncoder::new(oauth.access_secret.as_bytes().iter())) | |
.collect::<String>(); | |
let signature_base = { | |
let query = query_str(&auth_params); | |
match reqtype { | |
RequestType::Get => "GET&".chars(), | |
RequestType::Post => "POST&".chars(), | |
} | |
.chain(PercentEncoder::new(url.as_bytes().iter())) | |
.chain(std::iter::once('&')) | |
.chain(PercentEncoder::new(query.as_bytes().iter())) | |
.collect::<String>() | |
}; | |
auth_params.insert( | |
"oauth_signature".to_owned(), | |
hmac_sha1(signature_key.as_bytes(), signature_base.as_bytes())?, | |
); | |
let header_params = { | |
let mut s = auth_params | |
.iter() | |
.flat_map(|(k, v)| { | |
k.chars() | |
.chain(std::iter::once('=')) | |
.chain(std::iter::once('"')) | |
.chain(PercentEncoder::new(v.as_bytes().iter())) | |
.chain(std::iter::once('"')) | |
.chain(std::iter::once(',')) | |
}) | |
.collect::<String>(); | |
s.pop(); | |
s | |
}; | |
Ok(format!("{} {}", "Authorization: OAuth", header_params)) | |
} | |
fn auth_params(oauth: &Oauth) -> BTreeMap<String, String> { | |
// https://datatracker.ietf.org/doc/html/rfc5849#section-3.1 | |
BTreeMap::from([ | |
( | |
"oauth_consumer_key".to_owned(), | |
oauth.consumer_key.to_owned(), | |
), | |
("oauth_token".to_owned(), oauth.access_token.to_owned()), | |
("oauth_signature_method".to_owned(), "HMAC-SHA1".to_owned()), | |
("oauth_timestamp".to_owned(), oauth_timestamp()), | |
("oauth_nonce".to_owned(), oauth_nonce()), | |
("oauth_version".to_owned(), "1.0".to_owned()), | |
]) | |
} | |
fn oauth_timestamp() -> String { | |
use std::time::SystemTime; | |
SystemTime::now() | |
.duration_since(SystemTime::UNIX_EPOCH) | |
.unwrap() | |
.as_secs() | |
.to_string() | |
} | |
fn oauth_nonce() -> String { | |
use rand::Rng; | |
rand::thread_rng() | |
.sample_iter(rand::distributions::Alphanumeric) | |
.take(16) | |
.map(|c| c as char) | |
.collect() | |
} | |
fn query_str<T: AsRef<str>>(params: &BTreeMap<T, T>) -> String { | |
let mut s = params | |
.iter() | |
.flat_map(|(k, v)| { | |
k.as_ref() | |
.chars() | |
.chain(std::iter::once('=')) | |
.chain(PercentEncoder::new(v.as_ref().as_bytes().iter())) | |
.chain(std::iter::once('&')) | |
}) | |
.collect::<String>(); | |
s.pop(); // trim last b'&' | |
s | |
} | |
fn hmac_sha1(key: &[u8], data: &[u8]) -> Result<String> { | |
use openssl::hash::MessageDigest; | |
use openssl::pkey::PKey; | |
use openssl::sign::Signer; | |
let pkey = PKey::hmac(key)?; | |
let sig = Signer::new(MessageDigest::sha1(), &pkey)?.sign_oneshot_to_vec(data)?; | |
Ok(openssl::base64::encode_block(&sig)) | |
} | |
#[derive(Debug)] | |
pub enum Error { | |
String(String), | |
Io(std::io::Error), | |
Curl(curl::Error), | |
OpenSSL(openssl::error::ErrorStack), | |
} | |
pub type Result<T> = std::result::Result<T, Box<Error>>; | |
impl std::fmt::Display for Error { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
match self { | |
Error::String(s) => s.fmt(f), | |
Error::Io(e) => e.fmt(f), | |
Error::Curl(e) => e.fmt(f), | |
Error::OpenSSL(e) => e.fmt(f), | |
} | |
} | |
} | |
impl From<std::io::Error> for Box<Error> { | |
fn from(e: std::io::Error) -> Box<Error> { | |
Box::new(Error::Io(e)) | |
} | |
} | |
impl From<curl::Error> for Box<Error> { | |
fn from(e: curl::Error) -> Box<Error> { | |
Box::new(Error::Curl(e)) | |
} | |
} | |
impl From<openssl::error::ErrorStack> for Box<Error> { | |
fn from(e: openssl::error::ErrorStack) -> Box<Error> { | |
Box::new(Error::OpenSSL(e)) | |
} | |
} | |
struct PercentEncoder<I> { | |
iter: I, | |
state: PercentEncoderState, | |
} | |
impl<I> PercentEncoder<I> { | |
pub fn new(iter: I) -> PercentEncoder<I> { | |
PercentEncoder { | |
iter, | |
state: PercentEncoderState::ReadNext, | |
} | |
} | |
} | |
#[derive(Debug)] | |
enum PercentEncoderState { | |
ReadNext, | |
HexUpper(u8), | |
HexLower(u8), | |
} | |
// https://datatracker.ietf.org/doc/html/rfc5849#section-3.6 | |
impl<'a, I: Iterator<Item = &'a u8>> Iterator for PercentEncoder<I> { | |
type Item = char; | |
fn next(&mut self) -> Option<Self::Item> { | |
let hex = |v| match v { | |
s @ 0x0..=0x9 => Some(b'0' + s), | |
s @ 0xa..=0xf => Some(b'A' - 0xa + s), | |
_ => None, | |
}; | |
match self.state { | |
PercentEncoderState::ReadNext => match self.iter.next().cloned() { | |
sc @ Some(b'-' | b'.' | b'_' | b'~') => sc, | |
sc @ Some(c) if c.is_ascii_alphanumeric() => sc, | |
Some(c) => { | |
self.state = PercentEncoderState::HexUpper(c); | |
Some(b'%') | |
} | |
None => None, | |
}, | |
PercentEncoderState::HexUpper(c) => { | |
self.state = PercentEncoderState::HexLower(c); | |
hex((c >> 4) & 0xf) | |
} | |
PercentEncoderState::HexLower(c) => { | |
self.state = PercentEncoderState::ReadNext; | |
hex(c & 0x0f) | |
} | |
} | |
.map(|c| c as char) | |
} | |
} | |
#[test] | |
fn percent_encode() { | |
let s = "abc-._~ <>123".to_string(); | |
let e = PercentEncoder::new(s.as_bytes().iter()).collect::<String>(); | |
assert_eq!(&e, "abc-._~%20%3C%3E123"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment