aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2018-01-15 12:21:27 -0800
committeriximeow <me@iximeow.net>2018-01-15 12:24:15 -0800
commit7b84985857fd9bd1756439383f1a1ae82f9bd57a (patch)
treed1cf1a1891d7eb1ac05da483e3638f1b0b1c815a
parent935c78ce7d2aaabca269d81cff3459cef1084fbc (diff)
ensure all query string parameters are properly escaped
also un-escape html-encoded characters in DMs also distinguish errors in auth commands
-rw-r--r--src/commands/auth.rs28
-rw-r--r--src/commands/dm.rs7
-rw-r--r--src/commands/fav.rs4
-rw-r--r--src/commands/follow.rs5
-rw-r--r--src/commands/twete.rs33
-rw-r--r--src/main.rs102
-rw-r--r--src/tw/mod.rs15
7 files changed, 115 insertions, 79 deletions
diff --git a/src/commands/auth.rs b/src/commands/auth.rs
index 17503d5..08588dd 100644
--- a/src/commands/auth.rs
+++ b/src/commands/auth.rs
@@ -29,7 +29,14 @@ fn auth(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, dis
// callback set to oob so the user will later get a PIN.
// step 1: now present the correect oauth/authorize URL
// this is as far as auth can get (rest depends on user PIN'ing with the right thing)
- let res = queryer.raw_issue_request(::signed_api_req(&format!("{}?oauth_callback=oob", OAUTH_REQUEST_TOKEN_URL), hyper::Method::Post, &tweeter.app_key));
+ let res = queryer.raw_issue_request(
+ ::signed_api_req(
+ OAUTH_REQUEST_TOKEN_URL,
+ &vec![("oauth_callback", "oob")],
+ hyper::Method::Post,
+ &tweeter.app_key
+ )
+ );
match res {
Ok(bytes) =>
match std::str::from_utf8(&bytes) {
@@ -49,7 +56,7 @@ fn auth(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, dis
display_info.status("couldn't rebuild url".to_owned())
},
Err(e) =>
- display_info.status(format!("request token url error: {}", e))
+ display_info.status(format!("error starting auth: {}", e))
};
}
@@ -67,7 +74,15 @@ fn pin(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, disp
return;
}
- let res = queryer.raw_issue_request(::signed_api_req_with_token(&format!("{}?oauth_verifier={}", OAUTH_ACCESS_TOKEN_URL, line), hyper::Method::Post, &tweeter.app_key, &tweeter.WIP_auth.clone().unwrap()));
+ let res = queryer.raw_issue_request(
+ ::signed_api_req_with_token(
+ OAUTH_ACCESS_TOKEN_URL,
+ &vec![("oauth_verifier", &line)],
+ hyper::Method::Post,
+ &tweeter.app_key,
+ &tweeter.WIP_auth.clone().unwrap()
+ )
+ );
match res {
Ok(bytes) =>
match std::str::from_utf8(&bytes) {
@@ -97,7 +112,7 @@ fn pin(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, disp
secret: as_map["oauth_token_secret"].to_owned()
};
- match queryer.do_api_get(::ACCOUNT_SETTINGS_URL, &tweeter.app_key, &user_credential) {
+ match queryer.do_api_get_noparam(::ACCOUNT_SETTINGS_URL, &tweeter.app_key, &user_credential) {
Ok(settings) => {
let user_handle = settings["screen_name"].as_str().unwrap().to_owned();
/*
@@ -105,7 +120,8 @@ fn pin(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, disp
* largely the same logic as `look_up_user`.
*/
let looked_up_user = queryer.do_api_get(
- &format!("{}?screen_name={}", ::USER_LOOKUP_URL, user_handle),
+ ::USER_LOOKUP_URL,
+ &vec![("screen_name", &user_handle)],
&tweeter.app_key,
&user_credential
).and_then(|json| tw::user::User::from_json(json));
@@ -136,6 +152,6 @@ fn pin(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, disp
display_info.status("couldn't rebuild url".to_owned())
},
Err(e) =>
- display_info.status(format!("request token url error: {}", e))
+ display_info.status(format!("pin submission error: {}", e))
};
}
diff --git a/src/commands/dm.rs b/src/commands/dm.rs
index 95f65b7..f6fcbfd 100644
--- a/src/commands/dm.rs
+++ b/src/commands/dm.rs
@@ -42,7 +42,12 @@ fn dm(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, displ
let encoded = ::url_encode(dm_text);
let result = match tweeter.current_profile() {
Some(user_profile) => {
- queryer.do_api_post(&format!("{}?text={}&screen_name={}", DM_CREATE_URL, encoded, normalized_handle), &tweeter.app_key, &user_profile.creds)
+ queryer.do_api_post(
+ DM_CREATE_URL,
+ &vec![("text", &encoded), ("screen_name", &normalized_handle)],
+ &tweeter.app_key,
+ &user_profile.creds
+ )
},
None => Err("No logged in user to DM as".to_owned())
};
diff --git a/src/commands/fav.rs b/src/commands/fav.rs
index 02ec7dd..d853a0d 100644
--- a/src/commands/fav.rs
+++ b/src/commands/fav.rs
@@ -23,7 +23,7 @@ fn unfav(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, di
Ok(twid) => {
if let Some(twete) = tweeter.retrieve_tweet(&twid) {
let result = match tweeter.current_profile() {
- Some(user_profile) => queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id), &tweeter.app_key, &user_profile.creds),
+ Some(user_profile) => queryer.do_api_post(UNFAV_TWEET_URL, &vec![("id", &twete.id)], &tweeter.app_key, &user_profile.creds),
None => Err("No logged in user to unfav from".to_owned())
};
match result {
@@ -55,7 +55,7 @@ fn fav(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, disp
// tweeter.to_twitter_tweet_id(twid)...
if let Some(twete) = tweeter.retrieve_tweet(&twid) {
let result = match tweeter.current_profile() {
- Some(user_profile) => queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id), &tweeter.app_key, &user_profile.creds),
+ Some(user_profile) => queryer.do_api_post(FAV_TWEET_URL, &vec![("id", &twete.id)], &tweeter.app_key, &user_profile.creds),
None => Err("No logged in user to fav from".to_owned())
};
match result {
diff --git a/src/commands/follow.rs b/src/commands/follow.rs
index bc767d5..cd046c6 100644
--- a/src/commands/follow.rs
+++ b/src/commands/follow.rs
@@ -19,7 +19,7 @@ fn unfl(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, dis
let screen_name = line.trim();
let result = match tweeter.current_profile() {
Some(user_profile) => {
- queryer.do_api_post(&format!("{}?screen_name={}", FOLLOW_URL, screen_name), &tweeter.app_key, &user_profile.creds)
+ queryer.do_api_post(FOLLOW_URL, &vec![("screen_name", &screen_name)], &tweeter.app_key, &user_profile.creds)
},
None => Err("No logged in user to unfollow from".to_owned())
};
@@ -45,7 +45,8 @@ fn fl(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, displ
format!(
"fl resp: {:?}",
queryer.do_api_post(
- &format!("{}?screen_name={}", UNFOLLOW_URL, screen_name),
+ UNFOLLOW_URL,
+ &vec![("screen_name", &screen_name)],
&tweeter.app_key,
&user_profile.creds
)
diff --git a/src/commands/twete.rs b/src/commands/twete.rs
index 450c225..eded0db 100644
--- a/src/commands/twete.rs
+++ b/src/commands/twete.rs
@@ -24,7 +24,7 @@ fn del(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, disp
// TODO this really converts twid to a TweetId::Twitter
if let Some(twitter_id) = tweeter.retrieve_tweet(&twid).map(|x| x.id.to_owned()) {
let result = match tweeter.current_profile() {
- Some(user_profile) => queryer.do_api_post(&format!("{}/{}.json", DEL_TWEET_URL, twitter_id), &tweeter.app_key, &user_profile.creds),
+ Some(user_profile) => queryer.do_api_post_noparam(&format!("{}/{}.json", DEL_TWEET_URL, twitter_id), &tweeter.app_key, &user_profile.creds),
None => Err("No logged in user to delete as".to_owned())
};
match result {
@@ -61,9 +61,13 @@ fn twete(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, di
}
pub fn send_twete(text: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, display_info: &mut DisplayInfo) {
- let substituted = ::url_encode(&text);
let result = match tweeter.current_profile() {
- Some(user_profile) => queryer.do_api_post(&format!("{}?status={}", CREATE_TWEET_URL, substituted), &tweeter.app_key, &user_profile.creds),
+ Some(user_profile) => queryer.do_api_post(
+ CREATE_TWEET_URL,
+ &vec![("status", &text)],
+ &tweeter.app_key,
+ &user_profile.creds
+ ),
None => Err("No logged in user to tweet as".to_owned())
};
match result {
@@ -202,7 +206,12 @@ pub fn send_reply(text: String, twid: TweetId, tweeter: &mut tw::TwitterCache, q
let substituted = ::url_encode(&text);
let result = match tweeter.current_profile() {
Some(user_profile) => {
- queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id), &tweeter.app_key, &user_creds)
+ queryer.do_api_post(
+ CREATE_TWEET_URL,
+ &vec![("status", &text), ("in_reply_to_status_id", &twete.id)],
+ &tweeter.app_key,
+ &user_creds
+ )
},
None => Err("No logged in user to tweet as".to_owned())
};
@@ -234,22 +243,18 @@ fn quote(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, di
match maybe_id {
Ok(twid) => {
if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { // TODO: no clone when this stops taking &mut self
- let substituted = ::url_encode(reply);
- let attachment_url = ::url_encode(
+ let attachment_url =
&format!(
"https://www.twitter.com/{}/status/{}",
tweeter.retrieve_user(&twete.author_id).unwrap().handle, // TODO: for now this is ok ish, if we got the tweet we have the author
twete.id
- )
- );
+ );
let result = match tweeter.current_profile() {
Some(user_profile) => {
queryer.do_api_post(
- &format!("{}?status={}&attachment_url={}",
- CREATE_TWEET_URL,
- substituted,
- attachment_url
- ),
+ CREATE_TWEET_URL,
+ &vec![("status", reply), ("attachment_url", attachment_url)],
+
&tweeter.app_key,
&user_profile.creds
)
@@ -291,7 +296,7 @@ fn retwete(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer,
if let Some(twitter_id) = tweeter.retrieve_tweet(&twid).map(|x| x.id.to_owned()) {
let result = match tweeter.current_profile() {
Some(user_profile) => {
- queryer.do_api_post(&format!("{}/{}.json", RT_TWEET_URL, twitter_id), &tweeter.app_key, &user_profile.creds)
+ queryer.do_api_post_noparam(&format!("{}/{}.json", RT_TWEET_URL, twitter_id), &tweeter.app_key, &user_profile.creds)
},
None => Err("No logged in user to retweet as".to_owned())
};
diff --git a/src/main.rs b/src/main.rs
index 603108c..ebeb155 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -57,11 +57,20 @@ pub struct Queryer {
}
impl Queryer {
- fn do_api_get(&mut self, url: &str, app_cred: &tw::Credential, user_cred: &tw::Credential) -> Result<serde_json::Value, String> {
- self.issue_request(signed_api_get(url, app_cred, user_cred))
+ fn do_api_get_noparam(&mut self, url: &str, app_cred: &tw::Credential, user_cred: &tw::Credential) -> Result<serde_json::Value, String> {
+ self.do_api_get(url, &vec![], app_cred, user_cred)
}
- fn do_api_post(&mut self, url: &str, app_cred: &tw::Credential, user_cred: &tw::Credential) -> Result<serde_json::Value, String> {
- self.issue_request(signed_api_post(url, app_cred, user_cred))
+
+ fn do_api_get(&mut self, url: &str, params: &Vec<(&str, &str)>, app_cred: &tw::Credential, user_cred: &tw::Credential) -> Result<serde_json::Value, String> {
+ self.issue_request(signed_api_get(url, params, app_cred, user_cred))
+ }
+
+ fn do_api_post_noparam(&mut self, url: &str, app_cred: &tw::Credential, user_cred: &tw::Credential) -> Result<serde_json::Value, String> {
+ self.do_api_post(url, &vec![], app_cred, user_cred)
+ }
+
+ fn do_api_post(&mut self, url: &str, params: &Vec<(&str, &str)>, app_cred: &tw::Credential, user_cred: &tw::Credential) -> Result<serde_json::Value, String> {
+ self.issue_request(signed_api_post(url, params, app_cred, user_cred))
}
/*
fn do_web_req(&mut self, url: &str) -> Option<serde_json::Value> {
@@ -126,23 +135,27 @@ fn signed_web_get(url: &str) -> hyper::client::Request {
}
*/
-fn signed_api_post(url: &str, app_cred: &tw::Credential, user_cred: &tw::Credential) -> hyper::client::Request {
- signed_api_req_with_token(url, Method::Post, app_cred, user_cred)
+fn signed_api_post(url: &str, params: &Vec<(&str, &str)>, app_cred: &tw::Credential, user_cred: &tw::Credential) -> hyper::client::Request {
+ signed_api_req_with_token(url, params, Method::Post, app_cred, user_cred)
+}
+
+fn signed_api_get(url: &str, params: &Vec<(&str, &str)>, app_cred: &tw::Credential, user_cred: &tw::Credential) -> hyper::client::Request {
+ signed_api_req_with_token(url, params, Method::Get, app_cred, user_cred)
}
-fn signed_api_get(url: &str, app_cred: &tw::Credential, user_cred: &tw::Credential) -> hyper::client::Request {
- signed_api_req_with_token(url, Method::Get, app_cred, user_cred)
+fn signed_api_req_with_token(url: &str, params: &Vec<(&str, &str)>, method: Method, app_cred: &tw::Credential, user_cred: &tw::Credential) -> hyper::client::Request {
+ inner_signed_api_req(url, params, method, app_cred, Some(user_cred))
}
-fn signed_api_req_with_token(url: &str, method: Method, app_cred: &tw::Credential, user_cred: &tw::Credential) -> hyper::client::Request {
- inner_signed_api_req(url, method, app_cred, Some(user_cred))
+fn signed_api_req_no_params(url: &str, method: Method, app_cred: &tw::Credential) -> hyper::client::Request {
+ inner_signed_api_req(url, &vec![], method, app_cred, None)
}
-fn signed_api_req(url: &str, method: Method, app_cred: &tw::Credential) -> hyper::client::Request {
- inner_signed_api_req(url, method, app_cred, None)
+fn signed_api_req(url: &str, params: &Vec<(&str, &str)>, method: Method, app_cred: &tw::Credential) -> hyper::client::Request {
+ inner_signed_api_req(url, params, method, app_cred, None)
}
-fn inner_signed_api_req(url: &str, method: Method, app_cred: &tw::Credential, maybe_user_cred: Option<&tw::Credential>) -> hyper::client::Request {
+fn inner_signed_api_req(url: &str, params: &Vec<(&str, &str)>, method: Method, app_cred: &tw::Credential, maybe_user_cred: Option<&tw::Credential>) -> hyper::client::Request {
// let params: Vec<(String, String)> = vec![("track".to_string(), "london".to_string())];
let method_string = match method {
Method::Get => "GET",
@@ -150,10 +163,19 @@ fn inner_signed_api_req(url: &str, method: Method, app_cred: &tw::Credential, ma
_ => panic!(format!("unsupported method {}", method))
};
- let params: Vec<(String, String)> = vec![];
- let _param_string: String = params.iter().map(|p| p.0.clone() + &"=".to_string() + &p.1).collect::<Vec<String>>().join("&");
+ let escaped = params.iter().map(|&(ref k, ref v)| format!("{}={}",
+ url_encode(k),
+ url_encode(v)
+ ));
+ let params_str = escaped.collect::<Vec<String>>().join("&");
+
+ let constructed_url = if params_str.len() > 0 {
+ format!("{}?{}", url, params_str)
+ } else {
+ url.to_owned()
+ };
- let parsed_url = url::Url::parse(url).unwrap();
+ let parsed_url = url::Url::parse(&constructed_url).unwrap();
let mut builder = oauthcli::OAuthAuthorizationHeaderBuilder::new(
method_string,
@@ -169,7 +191,7 @@ fn inner_signed_api_req(url: &str, method: Method, app_cred: &tw::Credential, ma
let header = builder.finish();
- let mut req = Request::new(method, url.parse().unwrap());
+ let mut req = Request::new(method, constructed_url.parse().unwrap());
{
let headers = req.headers_mut();
@@ -479,35 +501,21 @@ fn do_ui(
}
fn url_encode(s: &str) -> String {
- s
- .replace("%", "%25")
- .replace("+", "%2b")
- .replace(" ", "+")
- .replace("\\n", "%0a")
- .replace("\\r", "%0d")
- .replace("\\esc", "%1b")
- .replace("!", "%21")
- .replace("#", "%23")
- .replace("$", "%24")
- .replace("&", "%26")
- .replace("'", "%27")
- .replace("(", "%28")
- .replace(")", "%29")
- .replace("*", "%2a")
- .replace(",", "%2c")
- .replace("-", "%2d")
- .replace(".", "%2e")
- .replace("/", "%2f")
- .replace(":", "%3a")
- .replace(";", "%3b")
- .replace("<", "%3c")
- .replace("=", "%3d")
- .replace(">", "%3e")
- .replace("?", "%3f")
- .replace("@", "%40")
- .replace("[", "%5b")
- .replace("\\", "%5c")
- .replace("]", "%5d")
+ fn encode_byte(c: u8) -> String {
+ if c == 0x20 {
+ "+".to_string()
+ } else if (c > 0x40 && c <= 0x40 + 26) ||
+ (c > 0x60 && c <= 0x60 + 26) ||
+ (c >= 0x30 && c < 0x3a) ||
+ c == 0x2d || c == 0x2e || c == 0x5f || c == 0x7e {
+ String::from_utf8(vec![c]).unwrap()
+ } else {
+ String::from(format!("%{:2x}", c))
+ }
+ }
+ s.as_bytes().iter().map(|c| {
+ encode_byte(*c)
+ }).collect::<Vec<String>>().join("")
}
// let (twete_tx, twete_rx) = chan::sync::<Vec<u8>>(0);
@@ -536,7 +544,7 @@ fn connect_twitter_stream(
.connector(connector)
.build(&core.handle());
- let req = signed_api_get(STREAMURL, &app_cred, &user_cred);
+ let req = signed_api_get(STREAMURL, &vec![], &app_cred, &user_cred);
let work = client.request(req).and_then(|res| {
let status = res.status();
if status != hyper::StatusCode::Ok {
diff --git a/src/tw/mod.rs b/src/tw/mod.rs
index 9a14b11..63b8f07 100644
--- a/src/tw/mod.rs
+++ b/src/tw/mod.rs
@@ -571,10 +571,10 @@ impl TwitterProfile {
}
}
pub fn get_settings(&self, queryer: &mut ::Queryer, app_key: &Credential) -> Result<serde_json::Value, String> {
- queryer.do_api_get(::ACCOUNT_SETTINGS_URL, app_key, &self.creds)
+ queryer.do_api_get_noparam(::ACCOUNT_SETTINGS_URL, app_key, &self.creds)
}
pub fn get_followers(&self, queryer: &mut ::Queryer, app_key: &Credential) -> Result<serde_json::Value, String> {
- queryer.do_api_get(::GET_FOLLOWER_IDS_URL, app_key, &self.creds)
+ queryer.do_api_get_noparam(::GET_FOLLOWER_IDS_URL, app_key, &self.creds)
}
pub fn set_following(&mut self, user_ids: Vec<String>) -> (Vec<String>, Vec<String>) {
let uid_set = user_ids.into_iter().collect::<HashSet<String>>();
@@ -1096,17 +1096,15 @@ impl TwitterCache {
}
fn look_up_user(&mut self, id: &str, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> {
- let url = &format!("{}?user_id={}", ::USER_LOOKUP_URL, id);
match self.current_profile() {
- Some(ref user_profile) => queryer.do_api_get(url, &self.app_key, &user_profile.creds),
+ Some(ref user_profile) => queryer.do_api_get(::USER_LOOKUP_URL, &vec![("user_id", id)], &self.app_key, &user_profile.creds),
None => Err("No authorized user to conduct lookup".to_owned())
}
}
fn look_up_tweet(&mut self, id: &str, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> {
- let url = &format!("{}&id={}", ::TWEET_LOOKUP_URL, id);
match self.current_profile() {
- Some(ref user_profile) => queryer.do_api_get(url, &self.app_key, &user_profile.creds),
+ Some(ref user_profile) => queryer.do_api_get(::TWEET_LOOKUP_URL, &vec![("id", id)], &self.app_key, &user_profile.creds),
None => Err("No authorized user to conduct lookup".to_owned())
}
}
@@ -1202,7 +1200,10 @@ fn handle_twitter_dm(
// show DM
tweeter.cache_api_user(structure["direct_message"]["recipient"].clone());
tweeter.cache_api_user(structure["direct_message"]["sender"].clone());
- let dm_text = structure["direct_message"]["text"].as_str().unwrap().to_string();
+ let dm_text = structure["direct_message"]["text"].as_str().unwrap().to_string()
+ .replace("&amp;", "&")
+ .replace("&gt;", ">")
+ .replace("&lt;", "<");
let to = structure["direct_message"]["recipient_id_str"].as_str().unwrap().to_string();
let from = structure["direct_message"]["sender_id_str"].as_str().unwrap().to_string();
display_info.recv(display::Infos::DM(dm_text, from, to));