Skip to content

Commit 3716274

Browse files
committed
finish borrows self
1 parent f56524d commit 3716274

File tree

4 files changed

+282
-86
lines changed

4 files changed

+282
-86
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description = "Implementation of OAuth 1.0 Client without curl dependency"
66
documentation = "http://azyobuzin.github.io/rust-oauthcli/oauthcli/"
77
repository = "https://github.com/azyobuzin/rust-oauthcli"
88
keywords = ["oauth"]
9-
license = "WTFPL"
9+
license = "MIT"
1010

1111
[dependencies]
1212
rand = "0.3"

LICENSE.txt

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
oauthcli ©2015 azyobuzin
1+
The MIT License (MIT)
22

3-
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
4-
Version 2, December 2004
3+
Copyright (c) 2015-2016 azyobuzin
54

6-
Copyright (C) 2004 Sam Hocevar <[email protected]>
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
711

8-
Everyone is permitted to copy and distribute verbatim or modified
9-
copies of this license document, and changing it is allowed as long
10-
as the name is changed.
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
1114

12-
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
13-
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
14-
15-
0. You just DO WHAT THE FUCK YOU WANT TO.
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

src/lib.rs

Lines changed: 170 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
//! Yet Another OAuth 1.0 Client Library for Rust
2-
// TODO: Write examples.
2+
//!
3+
//! # Examples
4+
//! Basic sample:
5+
//!
6+
//! ```
7+
//! # extern crate url;
8+
//! # extern crate oauthcli;
9+
//! # use oauthcli::*;
10+
//! # fn main() {
11+
//! let url = url::Url::parse("http://example.com/").unwrap();
12+
//! let header =
13+
//! OAuthAuthorizationHeaderBuilder::new(
14+
//! "GET", &url, "consumer", "secret", SignatureMethod::HmacSha1)
15+
//! .token("token", "secret")
16+
//! .finish();
17+
//! # }
18+
//! ```
19+
//!
20+
//! If you use for Twitter, because of Twitter's bug, use `finish_for_twitter` method,
21+
//! and make sure to encode the request body with `OAUTH_ENCODE_SET`.
22+
//! For more detail, see [this article](http://azyobuzin.hatenablog.com/entry/2015/04/18/232516) (Japanese).
323
424
extern crate rand;
525
extern crate rustc_serialize;
@@ -13,11 +33,13 @@ pub mod security;
1333
use security::*;
1434
use std::ascii::AsciiExt;
1535
use std::borrow::{Borrow, Cow};
36+
use std::error::Error;
1637
use std::fmt::{self, Write};
1738
use std::iter;
1839
use rand::Rng;
1940
use rustc_serialize::base64::{self, ToBase64};
20-
use url::{Url, percent_encoding};
41+
use url::Url;
42+
use url::percent_encoding::{EncodeSet, PercentEncode, utf8_percent_encode};
2143

2244
/// Available `oauth_signature_method` types.
2345
#[derive(Copy, Debug, PartialEq, Eq, Clone, Hash)]
@@ -48,7 +70,7 @@ impl fmt::Display for SignatureMethod {
4870
#[allow(non_camel_case_types)]
4971
pub struct OAUTH_ENCODE_SET;
5072

51-
impl percent_encoding::EncodeSet for OAUTH_ENCODE_SET {
73+
impl EncodeSet for OAUTH_ENCODE_SET {
5274
fn contains(&self, byte: u8) -> bool {
5375
!((byte >= 0x30 && byte <= 0x39)
5476
|| (byte >= 0x41 && byte <= 0x5A)
@@ -58,8 +80,31 @@ impl percent_encoding::EncodeSet for OAUTH_ENCODE_SET {
5880
}
5981
}
6082

61-
fn percent_encode(input: &str) -> percent_encoding::PercentEncode<OAUTH_ENCODE_SET> {
62-
percent_encoding::utf8_percent_encode(input, OAUTH_ENCODE_SET)
83+
fn percent_encode(input: &str) -> PercentEncode<OAUTH_ENCODE_SET> {
84+
utf8_percent_encode(input, OAUTH_ENCODE_SET)
85+
}
86+
87+
#[derive(Copy, Debug, PartialEq, Eq, Clone, Hash)]
88+
pub enum ParseOAuthAuthorizationHeaderError {
89+
/// The input is violating auth-param format.
90+
FormatError,
91+
/// The input is not completely escaped with `OAUTH_ENCODE_SET`.
92+
EscapeError
93+
}
94+
95+
impl Error for ParseOAuthAuthorizationHeaderError {
96+
fn description(&self) -> &str {
97+
match *self {
98+
ParseOAuthAuthorizationHeaderError::FormatError => "The input is violating auth-param format",
99+
ParseOAuthAuthorizationHeaderError::EscapeError => "The input is not completely escaped with `OAUTH_ENCODE_SET`"
100+
}
101+
}
102+
}
103+
104+
impl fmt::Display for ParseOAuthAuthorizationHeaderError {
105+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106+
f.write_str(self.description())
107+
}
63108
}
64109

65110
/// `Authorization` header for OAuth.
@@ -69,28 +114,79 @@ fn percent_encode(input: &str) -> percent_encoding::PercentEncode<OAUTH_ENCODE_S
69114
/// # Example
70115
/// ```
71116
/// # use oauthcli::OAuthAuthorizationHeader;
72-
/// let header = OAuthAuthorizationHeader { auth_param: "oauth_consumer_key=...".to_string() };
73-
/// assert_eq!(header.to_string(), "OAuth oauth_consumer_key=...");
117+
/// let header: OAuthAuthorizationHeader = "oauth_consumer_key=\"foo\"".parse().unwrap();
118+
/// assert_eq!(header.to_string(), "OAuth oauth_consumer_key=\"foo\"");
74119
/// ```
75120
#[derive(Debug, Clone)]
76121
pub struct OAuthAuthorizationHeader {
122+
s: String
123+
}
124+
125+
impl OAuthAuthorizationHeader {
77126
/// `auth-param` in RFC 7235
78-
pub auth_param: String
127+
pub fn auth_param(&self) -> &str {
128+
&self.s
129+
}
130+
131+
pub fn auth_param_owned(self) -> String {
132+
self.s
133+
}
79134
}
80135

81136
impl fmt::Display for OAuthAuthorizationHeader {
82137
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83138
try!(f.write_str("OAuth "));
84-
f.write_str(&self.auth_param)
139+
f.write_str(&self.s)
85140
}
86141
}
87142

88143
impl std::str::FromStr for OAuthAuthorizationHeader {
89-
type Err = ();
144+
type Err = ParseOAuthAuthorizationHeaderError;
145+
146+
fn from_str(s: &str) -> Result<OAuthAuthorizationHeader, ParseOAuthAuthorizationHeaderError> {
147+
fn is_hex(x: u8) -> bool {
148+
(x >= 0x30 && x <= 0x39) ||
149+
(x >= 0x41 && x <= 0x46) ||
150+
(x >= 0x61 && x <= 0x66)
151+
}
152+
153+
fn check(s: &str) -> bool {
154+
let mut i = s.as_bytes().iter();
155+
while let Some(&c) = i.next() {
156+
match c {
157+
0x25 => {
158+
if let (Some(&a), Some(&b)) = (i.next(), i.next()) {
159+
if !(is_hex(a) && is_hex(b)) { return false; }
160+
} else {
161+
return false;
162+
}
163+
},
164+
c => if OAUTH_ENCODE_SET.contains(c) { return false; }
165+
}
166+
}
90167

91-
fn from_str(s: &str) -> Result<OAuthAuthorizationHeader, ()> {
92-
// クソ雑
93-
Ok(OAuthAuthorizationHeader { auth_param: s.to_owned() })
168+
true
169+
}
170+
171+
for pair in s.split(',').map(|x| x.trim()).filter(|x| x.len() > 0) {
172+
if let Some(equal_index) = pair.find('=') {
173+
if !check(pair[0..equal_index].trim_right()) {
174+
return Err(ParseOAuthAuthorizationHeaderError::EscapeError);
175+
}
176+
177+
let val = pair[equal_index+1..].trim_left();
178+
if !val.starts_with('"') || !val.ends_with('"') {
179+
return Err(ParseOAuthAuthorizationHeaderError::FormatError);
180+
}
181+
if !check(&val[1..val.len()-1]) {
182+
return Err(ParseOAuthAuthorizationHeaderError::EscapeError);
183+
}
184+
} else {
185+
return Err(ParseOAuthAuthorizationHeaderError::FormatError);
186+
}
187+
}
188+
189+
Ok(OAuthAuthorizationHeader { s: s.to_owned() })
94190
}
95191
}
96192

@@ -101,7 +197,7 @@ impl hyper::header::Scheme for OAuthAuthorizationHeader {
101197
}
102198

103199
fn fmt_scheme(&self, f: &mut fmt::Formatter) -> fmt::Result {
104-
f.write_str(&self.auth_param)
200+
f.write_str(&self.s)
105201
}
106202
}
107203

@@ -266,7 +362,54 @@ impl<'a> OAuthAuthorizationHeaderBuilder<'a> {
266362
self
267363
}
268364

269-
fn finish_impl(self, for_twitter: bool) -> OAuthAuthorizationHeader {
365+
fn signature(&self, oauth_params: &[(&'a str, &'a str)], for_twitter: bool) -> String {
366+
let mut key: String = percent_encode(&self.consumer_secret).collect();
367+
key.push('&');
368+
369+
if let &Some(ref x) = &self.token_secret {
370+
key.extend(percent_encode(&x));
371+
}
372+
373+
match &self.signature_method {
374+
&SignatureMethod::HmacSha1 => {
375+
let params = oauth_params.iter()
376+
.map(|&(k, v)| (k.into(), v.into()))
377+
.chain(self.parameters.iter()
378+
.map(|&(ref k, ref v)| (Cow::Borrowed(k.borrow()), Cow::Borrowed(v.borrow()))));
379+
380+
let params =
381+
if for_twitter {
382+
// Workaround for Twitter: don't re-encode the query
383+
let PercentEncodedParameters(mut x) = percent_encode_parameters(params);
384+
385+
if let Some(query) = self.url.query() {
386+
for pair in query.split('&').filter(|x| x.len() > 0) {
387+
let mut pair_iter = pair.splitn(2, '=');
388+
let key = pair_iter.next().unwrap();
389+
let val = pair_iter.next().unwrap_or("");
390+
x.push((key.into(), val.into()));
391+
}
392+
}
393+
394+
PercentEncodedParameters(x)
395+
} else {
396+
percent_encode_parameters(params.chain(self.url.query_pairs()))
397+
};
398+
399+
let mut base_string = self.method.to_ascii_uppercase();
400+
base_string.push('&');
401+
base_string.extend(percent_encode(&base_string_url(self.url)));
402+
base_string.push('&');
403+
base_string.extend(percent_encode(&normalize_parameters(params)));
404+
405+
hmac(key.as_bytes(), base_string.as_bytes(), Sha1)
406+
.to_base64(base64::STANDARD)
407+
},
408+
&SignatureMethod::Plaintext => key
409+
}
410+
}
411+
412+
fn finish_impl(&self, for_twitter: bool) -> OAuthAuthorizationHeader {
270413
let tmp_timestamp = self.timestamp.unwrap_or_else(gen_timestamp).to_string();
271414
let tmp_nonce;
272415
let oauth_params = {
@@ -276,71 +419,27 @@ impl<'a> OAuthAuthorizationHeaderBuilder<'a> {
276419
if let Some(ref x) = self.token { p.push(("oauth_token", x.borrow())) }
277420
p.push(("oauth_signature_method", self.signature_method.to_str()));
278421
p.push(("oauth_timestamp", &tmp_timestamp));
279-
p.push(("oauth_nonce", match self.nonce {
280-
Some(ref x) => x.borrow(),
281-
None => {
422+
p.push(("oauth_nonce", match &self.nonce {
423+
&Some(ref x) => x.borrow(),
424+
_ => {
282425
tmp_nonce = nonce();
283426
&tmp_nonce
284427
}
285428
}));
286-
if let Some(ref x) = self.callback { p.push(("oauth_callback", x.borrow())) }
287-
if let Some(ref x) = self.verifier { p.push(("oauth_verifier", x.borrow())) }
429+
if let &Some(ref x) = &self.callback { p.push(("oauth_callback", x.borrow())) }
430+
if let &Some(ref x) = &self.verifier { p.push(("oauth_verifier", x.borrow())) }
288431
if self.include_version { p.push(("oauth_version", "1.0")) }
289432

290433
p
291434
};
292435

293-
let signature = {
294-
let mut key: String = percent_encode(&self.consumer_secret).collect();
295-
key.push('&');
296-
297-
if let Some(x) = self.token_secret {
298-
key.extend(percent_encode(&x));
299-
}
300-
301-
match self.signature_method {
302-
SignatureMethod::HmacSha1 => {
303-
let params = oauth_params.iter()
304-
.map(|&(k, v)| (k.into(), v.into()))
305-
.chain(self.parameters.into_iter());
306-
307-
let params =
308-
if for_twitter {
309-
// Workaround for Twitter: don't re-encode the query
310-
let PercentEncodedParameters(mut x) = percent_encode_parameters(params);
311-
312-
if let Some(query) = self.url.query() {
313-
for pair in query.split('&').filter(|x| x.len() > 0) {
314-
let mut pair_iter = pair.splitn(2, '=');
315-
let key = pair_iter.next().unwrap();
316-
let val = pair_iter.next().unwrap_or("");
317-
x.push((key.into(), val.into()));
318-
}
319-
}
320-
321-
PercentEncodedParameters(x)
322-
} else {
323-
percent_encode_parameters(params.chain(self.url.query_pairs()))
324-
};
325-
326-
let mut base_string = self.method.to_ascii_uppercase();
327-
base_string.push('&');
328-
base_string.extend(percent_encode(&base_string_url(self.url)));
329-
base_string.push('&');
330-
base_string.extend(percent_encode(&normalize_parameters(params)));
331-
332-
hmac(key.as_bytes(), base_string.as_bytes(), Sha1)
333-
.to_base64(base64::STANDARD)
334-
},
335-
SignatureMethod::Plaintext => key
336-
}
337-
};
436+
let signature = self.signature(&oauth_params, for_twitter);
338437

339438
let mut oauth_params = self.realm.as_ref()
340439
.map(|x| ("realm", x.borrow()))
341440
.into_iter()
342441
.chain(oauth_params.into_iter())
343-
.chain(iter::once(("oauth_signature", signature.borrow())));
442+
.chain(iter::once(("oauth_signature", &signature[..])));
344443

345444
let mut result = String::new();
346445
let mut first = true;
@@ -350,21 +449,21 @@ impl<'a> OAuthAuthorizationHeaderBuilder<'a> {
350449
else { result.push(','); }
351450

352451
write!(&mut result, "{}=\"{}\"",
353-
percent_encode(&k), percent_encode(&v)).unwrap();
452+
percent_encode(k), percent_encode(v)).unwrap();
354453
}
355454

356-
OAuthAuthorizationHeader { auth_param: result }
455+
OAuthAuthorizationHeader { s: result }
357456
}
358457

359458
/// Generate `Authorization` header for OAuth.
360459
///
361460
/// # Panics
362461
/// This function will panic if `url` is not valid for HTTP or HTTPS.
363-
pub fn finish(self) -> OAuthAuthorizationHeader {
462+
pub fn finish(&self) -> OAuthAuthorizationHeader {
364463
self.finish_impl(false)
365464
}
366465

367-
pub fn finish_for_twitter(self) -> OAuthAuthorizationHeader {
466+
pub fn finish_for_twitter(&self) -> OAuthAuthorizationHeader {
368467
self.finish_impl(true)
369468
}
370469
}
@@ -394,7 +493,7 @@ pub fn authorization_header<P>(method: &str, url: Url, realm: Option<&str>,
394493
match (token, token_secret) {
395494
(Some(x), Some(y)) => { builder.token(x, y); },
396495
(None, None) => (),
397-
(Some(_), None) | (None, Some(_)) => panic!("Both `token` and `token_secret` parameter are required")
496+
_ => panic!("Both `token` and `token_secret` parameter are required")
398497
}
399498

400499
if let Some(x) = realm { builder.realm(x); }

0 commit comments

Comments
 (0)