PageRenderTime 50ms CodeModel.GetById 20ms app.highlight 21ms RepoModel.GetById 1ms app.codeStats 0ms

/Application/Core/OAuth.cs

http://yet-another-music-application.googlecode.com/
C# | 778 lines | 280 code | 60 blank | 438 comment | 26 complexity | 4625f7a450be939a2eba2ffa8d506dc3 MD5 | raw file
  1/**
  2 * OAuth.cs
  3 * 
  4 * Code to do OAuth stuff, in support of a cropper plugin that
  5 * sends a screen snap to TwitPic.com.
  6 *
  7 * There's one main class: OAuth.Manager.  It handles interaction
  8 * with the OAuth-enabled service, for requesting temporary tokens
  9 * (aka request tokens), as well as access tokens. It also provides
 10 * a convenient way to construct an oauth Authorization header for
 11 * use in any Http transaction.
 12 *
 13 * The code has been tested with Twitter and TwitPic, from a
 14 * desktop application.
 15 * 
 16 * * * * * * * * *
 17 * 
 18 * Copyright 2010 Dino Chiesa
 19 * 
 20 * This code was taken from TwitPic (www.twitpic.com) with
 21 * permission and is part of the Stoffi Music Player Project.
 22 * Visit our website at: stoffiplayer.com
 23 *
 24 * This program is free software; you can redistribute it and/or
 25 * modify it under the terms of the GNU General Public License
 26 * as published by the Free Software Foundation; either version
 27 * 3 of the License, or (at your option) any later version.
 28 * 
 29 * See stoffiplayer.com/license for more information.
 30 **/
 31
 32namespace OAuth
 33{
 34	using System;
 35	using System.Linq;
 36	using System.Collections.Generic;
 37	using System.Security.Cryptography;
 38
 39	/// <summary>
 40	///   A class to manage OAuth interactions.  This works with
 41	///   Twitter, not sure about other OAuth-enabled services.
 42	/// </summary>
 43	/// <remarks>
 44	///   <para>
 45	///     This class holds the relevant oauth parameters, and exposes
 46	///     methods that do things, based on those params.
 47	///   </para>
 48	///   <para>
 49	///     See http://dev.twitpic.com/docs/2/upload/ for an example of the
 50	///     oauth parameters.  The params include token, consumer_key,
 51	///     timestamp, version, and so on.  In the actual HTTP message, they
 52	///     all include the oauth_ prefix, so ..  oauth_token,
 53	///     oauth_timestamp, and so on.  You set these via a string indexer.
 54	///     If the instance of the class is called oauth, then to set
 55	///     the oauth_token parameter, you use oath["token"] in C#.
 56	///   </para>
 57	///   <para>
 58	///     This class automatically sets many of the required oauth parameters;
 59	///     this includes the timestamp, nonce, callback, and version parameters.
 60	///     (The callback param is initialized to 'oob'). You can reset any of
 61	///     these parameters as you see fit.  In many cases you won't have to.
 62	///   </para>
 63	///   <para>
 64	///     The public methods on the class include:
 65	///     AcquireRequestToken, AcquireAccessToken,
 66	///     GenerateCredsHeader, and GenerateAuthzHeader.  The
 67	///     first two are used only on the first run of an applicaiton,
 68	///     or after a user has explicitly de-authorized an application
 69	///     for use with OAuth.  Normally, the GenerateXxxHeader methods
 70	///     can be used repeatedly, when sending HTTP messages that
 71	///     require an OAuth Authorization header.
 72	///   </para>
 73	///   <para>
 74	///     The AcquireRequestToken and AcquireAccessToken methods
 75	///     actually send out HTTP messages.
 76	///   </para>
 77	///   <para>
 78	///     The GenerateXxxxHeaders are used when constructing and
 79	///     sending your own HTTP messages.
 80	///   </para>
 81	/// </remarks>
 82	public class Manager
 83	{
 84		/// <summary>
 85		///   The default public constructor.
 86		/// </summary>
 87		/// <remarks>
 88		///   <para>
 89		///     Initializes various fields to default values.
 90		///   </para>
 91		/// </remarks>
 92		public Manager()
 93		{
 94			_random = new Random();
 95			_params = new Dictionary<String, String>();
 96			_params["callback"] = "oob"; // presume "desktop" consumer
 97			_params["consumer_key"] = "";
 98			_params["consumer_secret"] = "";
 99			_params["timestamp"] = GenerateTimeStamp();
100			_params["nonce"] = GenerateNonce();
101			_params["signature_method"] = "HMAC-SHA1";
102			_params["signature"] = "";
103			_params["token"] = "";
104			_params["token_secret"] = "";
105			_params["version"] = "1.0";
106		}
107
108		/// <summary>
109		///   The constructor to use when using OAuth when you already
110		///   have an OAuth access token.
111		/// </summary>
112		/// <remarks>
113		///   <para>
114		///     The parameters for this constructor all have the
115		///     meaning you would expect.  The token and tokenSecret
116		///     are set in oauth_token, and oauth_token_secret.
117		///     These are *Access* tokens, obtained after a call
118		///     to AcquireAccessToken.  The application can store
119		///     those tokens and re-use them on successive runs.
120		///     For twitter at least, the access tokens never expire.
121		///   </para>
122		/// </remarks>
123		public Manager(string consumerKey,
124					   string consumerSecret,
125					   string token,
126					   string tokenSecret)
127			: this()
128		{
129			_params["consumer_key"] = consumerKey;
130			_params["consumer_secret"] = consumerSecret;
131			_params["token"] = token;
132			_params["token_secret"] = tokenSecret;
133		}
134
135		/// <summary>
136		///   string indexer to get or set oauth parameter values.
137		/// </summary>
138		/// <remarks>
139		///   <para>
140		///     Use the parameter name *without* the oauth_ prefix.
141		///     If you want to set the value for the oauth_token parameter
142		///     field in an HTTP message, then use oauth["token"].
143		///   </para>
144		///   <para>
145		///     The set of oauth param names known by this indexer includes:
146		///     callback, consumer_key, consumer_secret, timestamp, nonce,
147		///     signature_method, signature, token, token_secret, and version.
148		///   </para>
149		///   <para>
150		///     If you try setting a parameter with a name that is not known,
151		///     the setter will throw.  You cannot add new oauth parameters
152		///     using the setter on this indexer.
153		///   </para>
154		/// </remarks>
155		public string this[string ix]
156		{
157			get
158			{
159				if (_params.ContainsKey(ix))
160					return _params[ix];
161				throw new ArgumentException(ix);
162			}
163			set
164			{
165				if (!_params.ContainsKey(ix))
166					throw new ArgumentException(ix);
167				_params[ix] = value;
168			}
169		}
170
171
172		/// <summary>
173		/// Generate the timestamp for the signature.
174		/// </summary>
175		/// <returns>The timestamp, in string form.</returns>
176		private string GenerateTimeStamp()
177		{
178			TimeSpan ts = DateTime.UtcNow - _epoch;
179			return Convert.ToInt64(ts.TotalSeconds).ToString();
180		}
181
182		/// <summary>
183		///   Renews the nonce and timestamp on the oauth parameters.
184		/// </summary>
185		/// <remarks>
186		///   <para>
187		///     Each new request should get a new, current timestamp, and a
188		///     nonce. This helper method does both of those things. This gets
189		///     called before generating an authorization header, as for example
190		///     when the user of this class calls <see cref='AcquireRequestToken'>.
191		///   </para>
192		/// </remarks>
193		private void NewRequest()
194		{
195			_params["nonce"] = GenerateNonce();
196			_params["timestamp"] = GenerateTimeStamp();
197		}
198
199		/// <summary>
200		/// Generate an oauth nonce.
201		/// </summary>
202		/// <remarks>
203		///   <para>
204		///     According to RFC 5849, A nonce is a random string,
205		///     uniquely generated by the client to allow the server to
206		///     verify that a request has never been made before and
207		///     helps prevent replay attacks when requests are made over
208		///     a non-secure channel.  The nonce value MUST be unique
209		///     across all requests with the same timestamp, client
210		///     credentials, and token combinations.
211		///   </para>
212		///   <para>
213		///     One way to implement the nonce is just to use a
214		///     monotonically-increasing integer value.  It starts at zero and
215		///     increases by 1 for each new request or signature generated.
216		///     Keep in mind the nonce needs to be unique only for a given
217		///     timestamp!  So if your app makes less than one request per
218		///     second, then using a static nonce of "0" will work.
219		///   </para>
220		///   <para>
221		///     Most oauth nonce generation routines are waaaaay over-engineered,
222		///     and this one is no exception.
223		///   </para>
224		/// </remarks>
225		/// <returns>the nonce</returns>
226		private string GenerateNonce()
227		{
228			var sb = new System.Text.StringBuilder();
229			for (int i = 0; i < 8; i++)
230			{
231				int g = _random.Next(3);
232				switch (g)
233				{
234					case 0:
235						// lowercase alpha
236						sb.Append((char)(_random.Next(26) + 97), 1);
237						break;
238					default:
239						// numeric digits
240						sb.Append((char)(_random.Next(10) + 48), 1);
241						break;
242				}
243			}
244			return sb.ToString();
245		}
246
247
248		/// <summary>
249		/// Internal function to extract from a URL all query string
250		/// parameters that are not related to oauth - in other words all
251		/// parameters not begining with "oauth_".
252		/// </summary>
253		///
254		/// <remarks>
255		///   <para>
256		///     For example, given a url like http://foo?a=7&guff, the
257		///     returned value will be a Dictionary of string-to-string
258		///     relations.  There will be 2 entries in the Dictionary: "a"=>7,
259		///     and "guff"=>"".
260		///   </para>
261		/// </remarks>
262		///
263		/// <param name="queryString">The query string part of the Url</param>
264		///
265		/// <returns>A Dictionary containing the set of
266		/// parameter names and associated values</returns>
267		private Dictionary<String, String> ExtractQueryParameters(string queryString)
268		{
269			if (queryString.StartsWith("?"))
270				queryString = queryString.Remove(0, 1);
271
272			var result = new Dictionary<String, String>();
273
274			if (string.IsNullOrEmpty(queryString))
275				return result;
276
277			foreach (string s in queryString.Split('&'))
278			{
279				if (!string.IsNullOrEmpty(s) && !s.StartsWith("oauth_"))
280				{
281					if (s.IndexOf('=') > -1)
282					{
283						string[] temp = s.Split('=');
284						result.Add(temp[0], temp[1]);
285					}
286					else
287						result.Add(s, string.Empty);
288				}
289			}
290
291			return result;
292		}
293
294
295
296		/// <summary>
297		///   This is an oauth-compliant Url Encoder.  The default .NET
298		///   encoder outputs the percent encoding in lower case.  While this
299		///   is not a problem with the percent encoding defined in RFC 3986,
300		///   OAuth (RFC 5849) requires that the characters be upper case
301		///   throughout OAuth.
302		/// </summary>
303		///
304		/// <param name="value">The value to encode</param>
305		///
306		/// <returns>the Url-encoded version of that string</returns>
307		public static string UrlEncode(string value)
308		{
309			var result = new System.Text.StringBuilder();
310			foreach (char symbol in value)
311			{
312				if (unreservedChars.IndexOf(symbol) != -1)
313					result.Append(symbol);
314				else
315					result.Append('%' + String.Format("{0:X2}", (int)symbol));
316			}
317			return result.ToString();
318		}
319		private static string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
320
321
322		/// <summary>
323		/// Formats the list of request parameters into string a according
324		/// to the requirements of oauth. The resulting string could be used
325		/// in the Authorization header of the request.
326		/// </summary>
327		///
328		/// <remarks>
329		///   <para>
330		///     See http://dev.twitter.com/pages/auth#intro  for some
331		///     background.  The output of this is not suitable for signing.
332		///   </para>
333		///   <para>
334		///     There are 2 formats for specifying the list of oauth
335		///     parameters in the oauth spec: one suitable for signing, and
336		///     the other suitable for use within Authorization HTTP Headers.
337		///     This method emits a string suitable for the latter.
338		///   </para>
339		/// </remarks>
340		///
341		/// <param name="parameters">The Dictionary of
342		/// parameters. It need not be sorted.</param>
343		///
344		/// <returns>a string representing the parameters</returns>
345		private static string EncodeRequestParameters(ICollection<KeyValuePair<String, String>> p)
346		{
347			var sb = new System.Text.StringBuilder();
348			foreach (KeyValuePair<String, String> item in p.OrderBy(x => x.Key))
349			{
350				if (!String.IsNullOrEmpty(item.Value) &&
351					!item.Key.EndsWith("secret"))
352					sb.AppendFormat("oauth_{0}=\"{1}\", ",
353									item.Key,
354									UrlEncode(item.Value));
355			}
356
357			return sb.ToString().TrimEnd(' ').TrimEnd(',');
358		}
359
360
361
362		/// <summary>
363		///   Acquire a request token, from the given URI, using the given
364		///   HTTP method.
365		/// </summary>
366		///
367		/// <remarks>
368		///   <para>
369		///     To use this method, first instantiate a new Oauth.Manager object,
370		///     then set the callback param (oauth["callback"]='oob'). After the
371		///     call returns, you should direct the user to open a browser window
372		///     to the authorization page for the OAuth-enabled service. Or,
373		///     you can automatically open that page yourself. Do this with
374		///     System.Diagnostics.Process.Start(), passing the URL of the page.
375		///     There should be one query param: oauth_token with the value
376		///     obtained from oauth["token"].
377		///   </para>
378		///   <para>
379		///     According to the OAuth spec, you need to do this only ONCE per
380		///     application.  In other words, the first time the application
381		///     is run.  The normal oauth workflow is:  (1) get a request token,
382		///     (2) use that to acquire an access token (which requires explicit
383		///     user approval), then (3) using that access token, invoke
384		///     protected services.  The first two steps need to be done only
385		///     once per application.
386		///   </para>
387		///   <para>
388		///     For Twitter, at least, you can cache the access tokens
389		///     indefinitely; Twitter says they never expire.  However, other
390		///     oauth services may not do the same. Also: the user may at any
391		///     time revoke his authorization for your app, in which case you
392		///     need to perform the first 2 steps again.
393		///   </para>
394		/// </remarks>
395		///
396		/// <seealso cref='AcquireAccessToken'>
397		///
398		///
399		/// <example>
400		///   <para>
401		///     This example shows how to request an access token and key
402		///     from Twitter. It presumes you've already obtained a
403		///     consumer key and secret via app registration. Requesting
404		///     an access token is necessary only the first time you
405		///     contact the service. You can cache the access key and
406		///     token for subsequent runs, later.
407		///   </para>
408		///   <code>
409		///   // the URL to obtain a temporary "request token"
410		///   var rtUrl = "https://api.twitter.com/oauth/request_token";
411		///   var oauth = new OAuth.Manager();
412		///   // The consumer_{key,secret} are obtained via registration
413		///   oauth["consumer_key"] = "~~~CONSUMER_KEY~~~~";
414		///   oauth["consumer_secret"] = "~~~CONSUMER_SECRET~~~";
415		///   oauth.AcquireRequestToken(rtUrl, "POST");
416		///   var authzUrl = "https://api.twitter.com/oauth/authorize?oauth_token=" + oauth["token"];
417		///   System.Diagnostics.Process.Start(authzUrl);
418		///   // instruct the user to type in the PIN from that browser window
419		///   var pin = "...";
420		///   var atUrl = "https://api.twitter.com/oauth/access_token";
421		///   oauth.AcquireAccessToken(atUrl, "POST", pin);
422		///
423		///   // now, update twitter status using that access token
424		///   var appUrl = "http://api.twitter.com/1/statuses/update.xml?status=Hello";
425		///   var authzHeader = oauth.GenerateAuthzHeader(appUrl, "POST");
426		///   var request = (HttpWebRequest)WebRequest.Create(appUrl);
427		///   request.Method = "POST";
428		///   request.PreAuthenticate = true;
429		///   request.AllowWriteStreamBuffering = true;
430		///   request.Headers.Add("Authorization", authzHeader);
431		///
432		///   using (var response = (HttpWebResponse)request.GetResponse())
433		///   {
434		///     if (response.StatusCode != HttpStatusCode.OK)
435		///       MessageBox.Show("There's been a problem trying to tweet:" +
436		///                       Environment.NewLine +
437		///                       response.StatusDescription);
438		///   }
439		///   </code>
440		/// </example>
441		///
442		/// <returns>
443		///   a response object that contains the entire text of the response,
444		///   as well as extracted parameters. This method presumes the
445		///   response is query-param encoded. In other words,
446		///   poauth_token=foo&something_else=bar.
447		/// </returns>
448		public OAuthResponse AcquireRequestToken(string uri, string method)
449		{
450			NewRequest();
451			var authzHeader = GetAuthorizationHeader(uri, method);
452
453			// prepare the token request
454			var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri);
455			request.Headers.Add("Authorization", authzHeader);
456			request.Method = method;
457
458			using (var response = (System.Net.HttpWebResponse)request.GetResponse())
459			{
460				using (var reader = new System.IO.StreamReader(response.GetResponseStream()))
461				{
462					var r = new OAuthResponse(reader.ReadToEnd());
463					this["token"] = r["oauth_token"];
464
465					// Sometimes the request_token URL gives us an access token,
466					// with no user interaction required. Eg, when prior approval
467					// has already been granted.
468					try
469					{
470						if (r["oauth_token_secret"] != null)
471							this["token_secret"] = r["oauth_token_secret"];
472					}
473					catch { }
474					return r;
475				}
476			}
477		}
478
479
480		/// <summary>
481		///   Acquire an access token, from the given URI, using the given
482		///   HTTP method.
483		/// </summary>
484		///
485		/// <remarks>
486		///   <para>
487		///     To use this method, you must first set the oauth_token to the value
488		///     of the request token.  Eg, oauth["token"] = "whatever".
489		///   </para>
490		///   <para>
491		///     According to the OAuth spec, you need to do this only ONCE per
492		///     application.  In other words, the first time the application
493		///     is run.  The normal oauth workflow is:  (1) get a request token,
494		///     (2) use that to acquire an access token (which requires explicit
495		///     user approval), then (3) using that access token, invoke
496		///     protected services.  The first two steps need to be done only
497		///     once per application.
498		///   </para>
499		///   <para>
500		///     For Twitter, at least, you can cache the access tokens
501		///     indefinitely; Twitter says they never expire.  However, other
502		///     oauth services may not do the same. Also: the user may at any
503		///     time revoke his authorization for your app, in which case you
504		///     need to perform the first 2 steps again.
505		///   </para>
506		/// </remarks>
507		///
508		/// <seealso cref='AcquireRequestToken'>
509		///
510		/// <returns>
511		///   a response object that contains the entire text of the response,
512		///   as well as extracted parameters. This method presumes the
513		///   response is query-param encoded. In other words,
514		///   poauth_token=foo&something_else=bar.
515		/// </returns>
516		public OAuthResponse AcquireAccessToken(string uri, string method, string pin)
517		{
518			NewRequest();
519			_params["verifier"] = pin;
520
521			var authzHeader = GetAuthorizationHeader(uri, method);
522
523			// prepare the token request
524			var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri);
525			request.Headers.Add("Authorization", authzHeader);
526			request.Method = method;
527
528			using (var response = (System.Net.HttpWebResponse)request.GetResponse())
529			{
530				using (var reader = new System.IO.StreamReader(response.GetResponseStream()))
531				{
532					var r = new OAuthResponse(reader.ReadToEnd());
533					this["token"] = r["oauth_token"];
534					this["token_secret"] = r["oauth_token_secret"];
535					return r;
536				}
537			}
538		}
539
540
541		/// <summary>
542		///   Generate a string to be used in an Authorization header in
543		///   an HTTP request.
544		/// </summary>
545		/// <remarks>
546		///   <para>
547		///     This method assembles the available oauth_ parameters that
548		///     have been set in the Dictionary in this instance, produces
549		///     the signature base (As described by the OAuth spec, RFC 5849),
550		///     signs it, then re-formats the oauth_ parameters into the
551		///     appropriate form, including the oauth_signature value, and
552		///     returns the result.
553		///   </para>
554		///   <para>
555		///     If you pass in a non-null, non-empty realm, this method will
556		///     include the realm='foo' clause in the Authorization header.
557		///   </para>
558		/// </remarks>
559		///
560		/// <seealso cref='GenerateAuthzHeader'/>
561		public string GenerateCredsHeader(string uri, string method, string realm)
562		{
563			NewRequest();
564			var authzHeader = GetAuthorizationHeader(uri, method, realm);
565			return authzHeader;
566		}
567
568
569		/// <summary>
570		///   Generate a string to be used in an Authorization header in
571		///   an HTTP request.
572		/// </summary>
573		/// <remarks>
574		///   <para>
575		///     This method assembles the available oauth_ parameters that
576		///     have been set in the Dictionary in this instance, produces
577		///     the signature base (As described by the OAuth spec, RFC 5849),
578		///     signs it, then re-formats the oauth_ parameters into the
579		///     appropriate form, including the oauth_signature value, and
580		///     returns the result.
581		///   </para>
582		/// </remarks>
583		///
584		/// <example>
585		///   <para>
586		///     This example shows how to update the Twitter status
587		///     using the stored consumer key and secret, and a previously
588		///     obtained access token and secret.
589		///   </para>
590		///   <code>
591		///   var oauth = new OAuth.Manager();
592		///   oauth["consumer_key"]    = "~~ your stored consumer key ~~";
593		///   oauth["consumer_secret"] = "~~ your stored consumer secret ~~";
594		///   oauth["token"]           = "~~ your stored access token ~~";
595		///   oauth["token_secret"]    = "~~ your stored access secret ~~";
596		///   var appUrl = "http://api.twitter.com/1/statuses/update.xml?status=Hello";
597		///   var authzHeader = oauth.GenerateAuthzHeader(appUrl, "POST");
598		///   var request = (HttpWebRequest)WebRequest.Create(appUrl);
599		///   request.Method = "POST";
600		///   request.PreAuthenticate = true;
601		///   request.AllowWriteStreamBuffering = true;
602		///   request.Headers.Add("Authorization", authzHeader);
603		///
604		///   using (var response = (HttpWebResponse)request.GetResponse())
605		///   {
606		///     if (response.StatusCode != HttpStatusCode.OK)
607		///       MessageBox.Show("There's been a problem trying to tweet:" +
608		///                       Environment.NewLine +
609		///                       response.StatusDescription);
610		///   }
611		///   </code>
612		/// </example>
613		/// <seealso cref='GenerateCredsHeader'/>
614		public string GenerateAuthzHeader(string uri, string method)
615		{
616			NewRequest();
617			var authzHeader = GetAuthorizationHeader(uri, method, null);
618			return authzHeader;
619		}
620
621		private string GetAuthorizationHeader(string uri, string method)
622		{
623			return GetAuthorizationHeader(uri, method, null);
624		}
625
626		private string GetAuthorizationHeader(string uri, string method, string realm)
627		{
628			if (string.IsNullOrEmpty(this._params["consumer_key"]))
629				throw new ArgumentNullException("consumer_key");
630
631			if (string.IsNullOrEmpty(this._params["signature_method"]))
632				throw new ArgumentNullException("signature_method");
633
634			Sign(uri, method);
635
636			var erp = EncodeRequestParameters(this._params);
637			return (String.IsNullOrEmpty(realm))
638				? "OAuth " + erp
639				: String.Format("OAuth realm=\"{0}\", ", realm) + erp;
640		}
641
642
643		private void Sign(string uri, string method)
644		{
645			var signatureBase = GetSignatureBase(uri, method);
646			var hash = GetHash();
647
648			byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes(signatureBase);
649			byte[] hashBytes = hash.ComputeHash(dataBuffer);
650
651			this["signature"] = Convert.ToBase64String(hashBytes);
652		}
653
654		/// <summary>
655		/// Formats the list of request parameters into "signature base" string as
656		/// defined by RFC 5849.  This will then be MAC'd with a suitable hash.
657		/// </summary>
658		private string GetSignatureBase(string url, string method)
659		{
660			// normalize the URI
661			var uri = new Uri(url);
662			var normUrl = string.Format("{0}://{1}", uri.Scheme, uri.Host);
663			if (!((uri.Scheme == "http" && uri.Port == 80) ||
664				  (uri.Scheme == "https" && uri.Port == 443)))
665				normUrl += ":" + uri.Port;
666
667			normUrl += uri.AbsolutePath;
668
669			// the sigbase starts with the method and the encoded URI
670			var sb = new System.Text.StringBuilder();
671			sb.Append(method)
672				.Append('&')
673				.Append(UrlEncode(normUrl))
674				.Append('&');
675
676			// the parameters follow - all oauth params plus any params on
677			// the uri
678			// each uri may have a distinct set of query params
679			var p = ExtractQueryParameters(uri.Query);
680			// add all non-empty params to the "current" params
681			foreach (var p1 in this._params)
682			{
683				// Exclude all oauth params that are secret or
684				// signatures; any secrets should be kept to ourselves,
685				// and any existing signature will be invalid.
686				if (!String.IsNullOrEmpty(this._params[p1.Key]) &&
687					!p1.Key.EndsWith("_secret") &&
688					!p1.Key.EndsWith("signature"))
689					p.Add("oauth_" + p1.Key, p1.Value);
690			}
691
692			// concat+format all those params
693			var sb1 = new System.Text.StringBuilder();
694			foreach (KeyValuePair<String, String> item in p.OrderBy(x => x.Key))
695			{
696				// even "empty" params need to be encoded this way.
697				sb1.AppendFormat("{0}={1}&", item.Key, item.Value);
698			}
699
700			// append the UrlEncoded version of that string to the sigbase
701			sb.Append(UrlEncode(sb1.ToString().TrimEnd('&')));
702			var result = sb.ToString();
703			return result;
704		}
705
706
707
708		private HashAlgorithm GetHash()
709		{
710			if (this["signature_method"] != "HMAC-SHA1")
711				throw new NotImplementedException();
712
713			string keystring = string.Format("{0}&{1}",
714											 UrlEncode(this["consumer_secret"]),
715											 UrlEncode(this["token_secret"]));
716			var hmacsha1 = new HMACSHA1
717			{
718				Key = System.Text.Encoding.ASCII.GetBytes(keystring)
719			};
720			return hmacsha1;
721		}
722
723#if BROKEN
724        /// <summary>
725        ///   Return the oauth string that can be used in an Authorization
726        ///   header. All the oauth terms appear in the string, in alphabetical
727        ///   order.
728        /// </summary>
729        public string GetOAuthHeader()
730        {
731            return EncodeRequestParameters(this._params);
732        }
733#endif
734		private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0);
735		private Dictionary<String, String> _params;
736		private Random _random;
737	}
738
739
740	/// <summary>
741	///   A class to hold an OAuth response message.
742	/// </summary>
743	public class OAuthResponse
744	{
745		/// <summary>
746		///   All of the text in the response. This is useful if the app wants
747		///   to do its own parsing.
748		/// </summary>
749		public string AllText { get; set; }
750		private Dictionary<String, String> _params;
751
752		/// <summary>
753		///   a Dictionary of response parameters.
754		/// </summary>
755		public string this[string ix]
756		{
757			get
758			{
759				return _params[ix];
760			}
761		}
762
763
764		public OAuthResponse(string alltext)
765		{
766			AllText = alltext;
767			_params = new Dictionary<String, String>();
768			var kvpairs = alltext.Split('&');
769			foreach (var pair in kvpairs)
770			{
771				var kv = pair.Split('=');
772				_params.Add(kv[0], kv[1]);
773			}
774			// expected keys:
775			//   oauth_token, oauth_token_secret, user_id, screen_name, etc
776		}
777	}
778}