1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 // (c) 2008-2011 Moz (@syo68k)
4 // (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 // (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 // (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 // (c) 2011 spinor (@tplantd) <http://d.hatena.ne.jp/spinor/>
8 // All rights reserved.
10 // This file is part of OpenTween.
12 // This program is free software; you can redistribute it and/or modify it
13 // under the terms of the GNU General Public License as published by the Free
14 // Software Foundation; either version 3 of the License, or (at your option)
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 // You should have received a copy of the GNU General Public License along
23 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
24 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
25 // Boston, MA 02110-1301, USA.
27 using HttpConnection = OpenTween.HttpConnection;
28 using IHttpConnection = OpenTween.IHttpConnection;
29 using OAuthUtility = OpenTween.Connection.OAuthUtility;
30 using DateTime = System.DateTime;
31 using DateTimeKind = System.DateTimeKind;
32 using Random = System.Random;
33 using HttpWebRequest = System.Net.HttpWebRequest;
34 using HttpStatusCode = System.Net.HttpStatusCode;
35 using Uri = System.Uri;
36 using System.Collections.Generic; // for Dictionary<TKey, TValue>, List<T>, KeyValuePair<TKey, TValue>, SortedDictionary<TKey, TValue>
37 using CallbackDelegate = OpenTween.CallbackDelegate;
38 using StackFrame = System.Diagnostics.StackFrame;
39 using FileInfo = System.IO.FileInfo;
40 using Stream = System.IO.Stream;
41 using HttpWebResponse = System.Net.HttpWebResponse;
42 using WebException = System.Net.WebException;
43 using WebExceptionStatus = System.Net.WebExceptionStatus;
44 using Exception = System.Exception;
45 using NameValueCollection = System.Collections.Specialized.NameValueCollection;
46 using Convert = System.Convert;
47 using InvalidDataException = System.IO.InvalidDataException;
48 using UriBuilder = System.UriBuilder;
49 using Environment = System.Environment;
50 using StringBuilder = System.Text.StringBuilder;
51 using HttpRequestHeader = System.Net.HttpRequestHeader;
52 using HMACSHA1 = System.Security.Cryptography.HMACSHA1;
53 using Encoding = System.Text.Encoding;
59 /// OAuth認証を使用するHTTP通信。HMAC-SHA1固定
62 /// 使用前に認証情報を設定する。認証確認を伴う場合はAuthenticate系のメソッドを、認証不要な場合はInitializeを呼ぶこと。
64 abstract public class HttpConnectionOAuth : HttpConnection, IHttpConnection
67 /// OAuthのアクセストークン。永続化可能(ユーザー取り消しの可能性はある)。
69 private string token = "";
72 /// OAuthの署名作成用秘密アクセストークン。永続化可能(ユーザー取り消しの可能性はある)。
74 private string tokenSecret = "";
79 protected string consumerKey;
82 /// OAuthの署名作成用秘密コンシューマーデータ
84 protected string consumerSecret;
87 /// 認証成功時の応答でユーザー情報を取得する場合のキー。設定しない場合は、AuthUsernameもブランクのままとなる
89 private string userIdentKey = "";
92 /// 認証成功時の応答でユーザーID情報を取得する場合のキー。設定しない場合は、AuthUserIdもブランクのままとなる
94 private string userIdIdentKey = "";
97 /// 認証完了時の応答からuserIdentKey情報に基づいて取得するユーザー情報
99 private string authorizedUsername = "";
102 /// 認証完了時の応答からuserIdentKey情報に基づいて取得するユーザー情報
104 private long authorizedUserId;
107 /// Stream用のHttpWebRequest
109 private HttpWebRequest streamReq = null;
112 /// OAuth認証で指定のURLとHTTP通信を行い、結果を返す
114 /// <param name="method">HTTP通信メソッド(GET/HEAD/POST/PUT/DELETE)</param>
115 /// <param name="requestUri">通信先URI</param>
116 /// <param name="param">GET時のクエリ、またはPOST時のエンティティボディ</param>
117 /// <param name="content">[OUT]HTTP応答のボディデータ</param>
118 /// <param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。必要なヘッダ名を事前に設定しておくこと</param>
119 /// <param name="callback">処理終了直前に呼ばれるコールバック関数のデリゲート 不要な場合はnullを渡すこと</param>
120 /// <returns>HTTP応答のステータスコード</returns>
121 public HttpStatusCode GetContent( string method,
123 Dictionary< string, string > param,
125 Dictionary< string, string > headerInfo,
126 CallbackDelegate callback )
129 if ( string.IsNullOrEmpty( token ) )
130 return HttpStatusCode.Unauthorized;
132 HttpWebRequest webReq = this.CreateRequest( method, requestUri, param, gzip: true );
134 this.AppendOAuthInfo( webReq, param, token, tokenSecret );
137 if ( content == null )
138 code = this.GetResponse( webReq, headerInfo );
140 code = this.GetResponse( webReq, out content, headerInfo );
142 if ( callback != null )
144 StackFrame frame = new StackFrame( 1 );
145 callback( frame.GetMethod().Name, code, headerInfo, content );
153 public HttpStatusCode GetContent( string method,
155 Dictionary< string, string > param,
156 List< KeyValuePair< string, IMediaItem > > binary,
158 Dictionary< string, string > headerInfo,
159 CallbackDelegate callback )
162 if ( string.IsNullOrEmpty( token ) )
163 return HttpStatusCode.Unauthorized;
165 HttpWebRequest webReq = this.CreateRequest( method, requestUri, param, binary );
167 this.AppendOAuthInfo( webReq, null, token, tokenSecret );
170 if ( content == null )
171 code = this.GetResponse( webReq, headerInfo );
173 code = this.GetResponse( webReq, out content, headerInfo );
175 if ( callback != null )
177 StackFrame frame = new StackFrame( 1 );
178 callback( frame.GetMethod().Name, code, headerInfo, content );
184 /// OAuth認証で指定のURLとHTTP通信を行い、ストリームを返す
186 /// <param name="method">HTTP通信メソッド(GET/HEAD/POST/PUT/DELETE)</param>
187 /// <param name="requestUri">通信先URI</param>
188 /// <param name="param">GET時のクエリ、またはPOST時のエンティティボディ</param>
189 /// <param name="content">[OUT]HTTP応答のボディストリーム</param>
190 /// <returns>HTTP応答のステータスコード</returns>
191 public HttpStatusCode GetContent( string method,
193 Dictionary< string, string > param,
198 if ( string.IsNullOrEmpty( token ) )
199 return HttpStatusCode.Unauthorized;
202 this.streamReq = this.CreateRequest( method, requestUri, param );
203 // User-Agent指定がある場合は付加
204 if ( !string.IsNullOrEmpty( userAgent ) )
205 this.streamReq.UserAgent = userAgent;
208 this.AppendOAuthInfo( this.streamReq, param, token, tokenSecret );
212 HttpWebResponse webRes = (HttpWebResponse)this.streamReq.GetResponse();
213 content = webRes.GetResponseStream();
214 return webRes.StatusCode;
216 catch ( WebException ex )
218 if ( ex.Status == WebExceptionStatus.ProtocolError )
220 HttpWebResponse res = (HttpWebResponse)ex.Response;
221 return res.StatusCode;
227 public void RequestAbort()
231 if ( this.streamReq != null )
233 this.streamReq.Abort();
234 this.streamReq = null;
237 catch ( Exception ) {}
242 /// OAuth認証の開始要求(リクエストトークン取得)。PIN入力用の前段
245 /// 呼び出し元では戻されたurlをブラウザで開き、認証完了後PIN入力を受け付けて、リクエストトークンと共にAuthenticatePinFlowを呼び出す
247 /// <param name="requestTokenUrl">リクエストトークンの取得先URL</param>
248 /// <param name="authorizeUrl">ブラウザで開く認証用URLのベース</param>
249 /// <param name="requestToken">[OUT]認証要求で戻されるリクエストトークン。使い捨て</param>
250 /// <param name="authUri">[OUT]requestUriを元に生成された認証用URL。通常はリクエストトークンをクエリとして付加したUri</param>
251 /// <returns>取得結果真偽値</returns>
252 public bool AuthenticatePinFlowRequest( string requestTokenUrl, string authorizeUrl, ref string requestToken, ref Uri authUri )
255 authUri = this.GetAuthenticatePageUri( requestTokenUrl, authorizeUrl, ref requestToken );
256 if ( authUri == null )
262 /// OAuth認証のアクセストークン取得。PIN入力用の後段
265 /// 事前にAuthenticatePinFlowRequestを呼んで、ブラウザで認証後に表示されるPINを入力してもらい、その値とともに呼び出すこと
267 /// <param name="accessTokenUrl">アクセストークンの取得先URL</param>
268 /// <param name="requestToken">AuthenticatePinFlowRequestで取得したリクエストトークン</param>
269 /// <param name="pinCode">Webで認証後に表示されるPINコード</param>
270 /// <returns>取得結果真偽値</returns>
271 public HttpStatusCode AuthenticatePinFlow( string accessTokenUrl, string requestToken, string pinCode )
274 if ( string.IsNullOrEmpty( requestToken ) )
275 throw new InvalidOperationException( "Sequence error.(requestToken is blank)" );
279 NameValueCollection accessTokenData;
280 HttpStatusCode httpCode = this.GetOAuthToken( new Uri( accessTokenUrl ), pinCode, requestToken, null, ref content );
281 if ( httpCode != HttpStatusCode.OK )
283 accessTokenData = base.ParseQueryString( content );
285 if ( accessTokenData != null )
287 this.token = accessTokenData[ "oauth_token" ];
288 this.tokenSecret = accessTokenData[ "oauth_token_secret" ];
291 if ( !string.IsNullOrEmpty(this.userIdentKey) )
292 this.authorizedUsername = accessTokenData[ this.userIdentKey ];
294 this.authorizedUsername = "";
296 if ( !string.IsNullOrEmpty(this.userIdIdentKey) )
300 this.authorizedUserId = Convert.ToInt64( accessTokenData[ this.userIdIdentKey ] );
304 this.authorizedUserId = 0;
309 this.authorizedUserId = 0;
312 if ( string.IsNullOrEmpty(token) )
313 throw new InvalidDataException( "Token is null." );
314 return HttpStatusCode.OK;
318 throw new InvalidDataException( "Return value is null." );
322 public HttpStatusCode Authenticate(Uri accessTokenUrl, string username, string password, ref string content)
324 return this.AuthenticateXAuth(accessTokenUrl, username, password, ref content);
328 /// OAuth認証のアクセストークン取得。xAuth方式
330 /// <param name="accessTokenUrl">アクセストークンの取得先URL</param>
331 /// <param name="username">認証用ユーザー名</param>
332 /// <param name="password">認証用パスワード</param>
333 /// <returns>取得結果真偽値</returns>
334 public HttpStatusCode AuthenticateXAuth( Uri accessTokenUrl, string username, string password, ref string content )
337 if ( string.IsNullOrEmpty( username ) )
338 throw new ArgumentException( "username is null or empty", nameof(username) );
339 if ( string.IsNullOrEmpty( password ) )
340 throw new ArgumentException( "password is null or empty", nameof(password) );
343 Dictionary< string, string > parameter = new Dictionary< string, string >();
344 parameter.Add( "x_auth_mode", "client_auth" );
345 parameter.Add( "x_auth_username", username );
346 parameter.Add( "x_auth_password", password );
349 HttpStatusCode httpCode = this.GetOAuthToken( accessTokenUrl, "", "", parameter, ref content );
350 if ( httpCode != HttpStatusCode.OK )
352 NameValueCollection accessTokenData = base.ParseQueryString( content );
354 if ( accessTokenData != null )
356 this.token = accessTokenData[ "oauth_token" ];
357 this.tokenSecret = accessTokenData[ "oauth_token_secret" ];
360 if ( !string.IsNullOrEmpty(this.userIdentKey) )
361 this.authorizedUsername = accessTokenData[ this.userIdentKey ];
363 this.authorizedUsername = "";
365 if ( !string.IsNullOrEmpty(this.userIdIdentKey) )
369 this.authorizedUserId = Convert.ToInt64( accessTokenData[ this.userIdIdentKey ] );
373 this.authorizedUserId = 0;
378 this.authorizedUserId = 0;
381 if ( string.IsNullOrEmpty(token) )
382 throw new InvalidDataException( "Token is null." );
383 return HttpStatusCode.OK;
387 throw new InvalidDataException( "Return value is null." );
392 /// OAuth認証のリクエストトークン取得。リクエストトークンと組み合わせた認証用のUriも生成する
394 /// <param name="requestTokenUrl">リクエストトークンの取得先URL</param>
395 /// <param name="authorizeUrl">ブラウザで開く認証用URLのベース</param>
396 /// <param name="requestToken">[OUT]取得したリクエストトークン</param>
397 /// <returns>取得結果真偽値</returns>
398 private Uri GetAuthenticatePageUri( string requestTokenUrl, string authorizeUrl, ref string requestToken )
400 const string tokenKey = "oauth_token";
404 NameValueCollection reqTokenData;
405 if ( this.GetOAuthToken( new Uri( requestTokenUrl ), "", "", null, ref content, callbackUrl: "oob" ) != HttpStatusCode.OK )
407 reqTokenData = base.ParseQueryString( content );
409 if ( reqTokenData != null )
411 requestToken = reqTokenData[ tokenKey ];
413 UriBuilder ub = new UriBuilder( authorizeUrl );
414 ub.Query = string.Format( "{0}={1}", tokenKey, requestToken );
424 /// OAuth認証のトークン取得共通処理
426 /// <param name="requestUri">各種トークンの取得先URL</param>
427 /// <param name="pinCode">PINフロー時のアクセストークン取得時に設定。それ以外は空文字列</param>
428 /// <param name="requestToken">PINフロー時のリクエストトークン取得時に設定。それ以外は空文字列</param>
429 /// <param name="parameter">追加パラメータ。xAuthで使用</param>
430 /// <returns>取得結果のデータ。正しく取得出来なかった場合はNothing</returns>
431 private HttpStatusCode GetOAuthToken( Uri requestUri, string pinCode, string requestToken, Dictionary< string , string > parameter, ref string content, string callbackUrl = null )
433 HttpWebRequest webReq = null;
434 // HTTPリクエスト生成。PINコードもパラメータも未指定の場合はGETメソッドで通信。それ以外はPOST
435 if ( string.IsNullOrEmpty( pinCode ) && parameter != null )
436 webReq = this.CreateRequest( "GET", requestUri, null );
438 webReq = this.CreateRequest( "POST", requestUri, parameter ); // ボディに追加パラメータ書き込み
440 // OAuth関連パラメータ準備。追加パラメータがあれば追加
441 Dictionary< string, string > query = new Dictionary< string, string >();
442 if ( parameter != null )
443 foreach ( KeyValuePair< string, string > kvp in parameter )
444 query.Add( kvp.Key, kvp.Value );
446 // PINコードが指定されていればパラメータに追加
447 if ( ! string.IsNullOrEmpty( pinCode ) )
448 query.Add( "oauth_verifier", pinCode );
450 // コールバックURLが指定されていればパラメータに追加
451 if (!string.IsNullOrEmpty(callbackUrl))
452 query.Add("oauth_callback", callbackUrl);
454 // OAuth関連情報をHTTPリクエストに追加
455 this.AppendOAuthInfo( webReq, query, requestToken, "" );
458 Dictionary< string, string > header = new Dictionary< string, string >() { { "Date", "" } };
459 HttpStatusCode responseCode = this.GetResponse( webReq, out content, header );
460 if ( responseCode == HttpStatusCode.OK )
463 if ( !string.IsNullOrEmpty( header[ "Date" ] ) )
464 content += Environment.NewLine + "Check the Date & Time of this computer." + Environment.NewLine
465 + "Server:" + DateTime.Parse( header[ "Date" ] ).ToString() + " PC:" + DateTime.Now.ToString();
470 #region "OAuth認証用ヘッダ作成・付加処理"
472 /// HTTPリクエストにOAuth関連ヘッダを追加
474 /// <param name="webRequest">追加対象のHTTPリクエスト</param>
475 /// <param name="query">OAuth追加情報+クエリ or POSTデータ</param>
476 /// <param name="token">アクセストークン、もしくはリクエストトークン。未取得なら空文字列</param>
477 /// <param name="tokenSecret">アクセストークンシークレット。認証処理では空文字列</param>
478 protected virtual void AppendOAuthInfo( HttpWebRequest webRequest, Dictionary< string, string > query, string token, string tokenSecret )
480 var credential = OAuthUtility.CreateAuthorization( webRequest.Method, webRequest.RequestUri, query,
481 this.consumerKey, this.consumerSecret, token, tokenSecret );
483 webRequest.Headers.Add( HttpRequestHeader.Authorization, credential );
485 #endregion // OAuth認証用ヘッダ作成・付加処理
488 /// 初期化。各種トークンの設定とユーザー識別情報設定
490 /// <param name="consumerKey">コンシューマー鍵</param>
491 /// <param name="consumerSecret">コンシューマー秘密鍵</param>
492 /// <param name="accessToken">アクセストークン</param>
493 /// <param name="accessTokenSecret">アクセストークン秘密鍵</param>
494 /// <param name="userIdentifier">アクセストークン取得時に得られるユーザー識別情報。不要なら空文字列</param>
495 public void Initialize( string consumerKey, string consumerSecret,
496 string accessToken, string accessTokenSecret,
497 string userIdentifier, string userIdIdentifier )
499 this.consumerKey = consumerKey;
500 this.consumerSecret = consumerSecret;
501 this.token = accessToken;
502 this.tokenSecret = accessTokenSecret;
503 this.userIdentKey = userIdentifier;
504 this.userIdIdentKey = userIdIdentifier;
508 /// 初期化。各種トークンの設定とユーザー識別情報設定
510 /// <param name="consumerKey">コンシューマー鍵</param>
511 /// <param name="consumerSecret">コンシューマー秘密鍵</param>
512 /// <param name="accessToken">アクセストークン</param>
513 /// <param name="accessTokenSecret">アクセストークン秘密鍵</param>
514 /// <param name="username">認証済みユーザー名</param>
515 /// <param name="userIdentifier">アクセストークン取得時に得られるユーザー識別情報。不要なら空文字列</param>
516 public void Initialize( string consumerKey, string consumerSecret,
517 string accessToken, string accessTokenSecret,
518 string username, long userId,
519 string userIdentifier, string userIdIdentifier )
521 this.Initialize( consumerKey, consumerSecret, accessToken, accessTokenSecret, userIdentifier, userIdIdentifier );
522 this.authorizedUsername = username;
523 this.authorizedUserId = userId;
529 public string AccessToken
531 get { return this.token; }
537 public string AccessTokenSecret
539 get { return this.tokenSecret; }
545 public string AuthUsername
547 get { return this.authorizedUsername; }
553 public long AuthUserId
555 get { return this.authorizedUserId; }
556 set { this.authorizedUserId = value; }