1
1
//! 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).
3
23
4
24
extern crate rand;
5
25
extern crate rustc_serialize;
@@ -13,11 +33,13 @@ pub mod security;
13
33
use security:: * ;
14
34
use std:: ascii:: AsciiExt ;
15
35
use std:: borrow:: { Borrow , Cow } ;
36
+ use std:: error:: Error ;
16
37
use std:: fmt:: { self , Write } ;
17
38
use std:: iter;
18
39
use rand:: Rng ;
19
40
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} ;
21
43
22
44
/// Available `oauth_signature_method` types.
23
45
#[ derive( Copy , Debug , PartialEq , Eq , Clone , Hash ) ]
@@ -48,7 +70,7 @@ impl fmt::Display for SignatureMethod {
48
70
#[ allow( non_camel_case_types) ]
49
71
pub struct OAUTH_ENCODE_SET ;
50
72
51
- impl percent_encoding :: EncodeSet for OAUTH_ENCODE_SET {
73
+ impl EncodeSet for OAUTH_ENCODE_SET {
52
74
fn contains ( & self , byte : u8 ) -> bool {
53
75
!( ( byte >= 0x30 && byte <= 0x39 )
54
76
|| ( byte >= 0x41 && byte <= 0x5A )
@@ -58,8 +80,31 @@ impl percent_encoding::EncodeSet for OAUTH_ENCODE_SET {
58
80
}
59
81
}
60
82
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
+ }
63
108
}
64
109
65
110
/// `Authorization` header for OAuth.
@@ -69,28 +114,79 @@ fn percent_encode(input: &str) -> percent_encoding::PercentEncode<OAUTH_ENCODE_S
69
114
/// # Example
70
115
/// ```
71
116
/// # 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\" ");
74
119
/// ```
75
120
#[ derive( Debug , Clone ) ]
76
121
pub struct OAuthAuthorizationHeader {
122
+ s : String
123
+ }
124
+
125
+ impl OAuthAuthorizationHeader {
77
126
/// `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
+ }
79
134
}
80
135
81
136
impl fmt:: Display for OAuthAuthorizationHeader {
82
137
fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
83
138
try!( f. write_str ( "OAuth " ) ) ;
84
- f. write_str ( & self . auth_param )
139
+ f. write_str ( & self . s )
85
140
}
86
141
}
87
142
88
143
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
+ }
90
167
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 ( ) } )
94
190
}
95
191
}
96
192
@@ -101,7 +197,7 @@ impl hyper::header::Scheme for OAuthAuthorizationHeader {
101
197
}
102
198
103
199
fn fmt_scheme ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
104
- f. write_str ( & self . auth_param )
200
+ f. write_str ( & self . s )
105
201
}
106
202
}
107
203
@@ -266,7 +362,54 @@ impl<'a> OAuthAuthorizationHeaderBuilder<'a> {
266
362
self
267
363
}
268
364
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 {
270
413
let tmp_timestamp = self . timestamp . unwrap_or_else ( gen_timestamp) . to_string ( ) ;
271
414
let tmp_nonce;
272
415
let oauth_params = {
@@ -276,71 +419,27 @@ impl<'a> OAuthAuthorizationHeaderBuilder<'a> {
276
419
if let Some ( ref x) = self . token { p. push ( ( "oauth_token" , x. borrow ( ) ) ) }
277
420
p. push ( ( "oauth_signature_method" , self . signature_method . to_str ( ) ) ) ;
278
421
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
+ _ => {
282
425
tmp_nonce = nonce ( ) ;
283
426
& tmp_nonce
284
427
}
285
428
} ) ) ;
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 ( ) ) ) }
288
431
if self . include_version { p. push ( ( "oauth_version" , "1.0" ) ) }
289
432
290
433
p
291
434
} ;
292
435
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) ;
338
437
339
438
let mut oauth_params = self . realm . as_ref ( )
340
439
. map ( |x| ( "realm" , x. borrow ( ) ) )
341
440
. into_iter ( )
342
441
. chain ( oauth_params. into_iter ( ) )
343
- . chain ( iter:: once ( ( "oauth_signature" , signature. borrow ( ) ) ) ) ;
442
+ . chain ( iter:: once ( ( "oauth_signature" , & signature[ .. ] ) ) ) ;
344
443
345
444
let mut result = String :: new ( ) ;
346
445
let mut first = true ;
@@ -350,21 +449,21 @@ impl<'a> OAuthAuthorizationHeaderBuilder<'a> {
350
449
else { result. push ( ',' ) ; }
351
450
352
451
write ! ( & mut result, "{}=\" {}\" " ,
353
- percent_encode( & k) , percent_encode( & v) ) . unwrap ( ) ;
452
+ percent_encode( k) , percent_encode( v) ) . unwrap ( ) ;
354
453
}
355
454
356
- OAuthAuthorizationHeader { auth_param : result }
455
+ OAuthAuthorizationHeader { s : result }
357
456
}
358
457
359
458
/// Generate `Authorization` header for OAuth.
360
459
///
361
460
/// # Panics
362
461
/// 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 {
364
463
self . finish_impl ( false )
365
464
}
366
465
367
- pub fn finish_for_twitter ( self ) -> OAuthAuthorizationHeader {
466
+ pub fn finish_for_twitter ( & self ) -> OAuthAuthorizationHeader {
368
467
self . finish_impl ( true )
369
468
}
370
469
}
@@ -394,7 +493,7 @@ pub fn authorization_header<P>(method: &str, url: Url, realm: Option<&str>,
394
493
match ( token, token_secret) {
395
494
( Some ( x) , Some ( y) ) => { builder. token ( x, y) ; } ,
396
495
( 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" )
398
497
}
399
498
400
499
if let Some ( x) = realm { builder. realm ( x) ; }
0 commit comments