1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
|
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
namespace DotNetOpenId {
internal static class UriUtil {
/// <summary>
/// Concatenates a list of name-value pairs as key=value&key=value,
/// taking care to properly encode each key and value for URL
/// transmission. No ? is prefixed to the string.
/// </summary>
public static string CreateQueryString(IDictionary<string, string> args) {
if (args == null) throw new ArgumentNullException("args");
if (args.Count == 0) return string.Empty;
StringBuilder sb = new StringBuilder(args.Count * 10);
foreach (var p in args) {
sb.Append(HttpUtility.UrlEncode(p.Key));
sb.Append('=');
sb.Append(HttpUtility.UrlEncode(p.Value));
sb.Append('&');
}
sb.Length--; // remove trailing &
return sb.ToString();
}
/// <summary>
/// Concatenates a list of name-value pairs as key=value&key=value,
/// taking care to properly encode each key and value for URL
/// transmission. No ? is prefixed to the string.
/// </summary>
public static string CreateQueryString(NameValueCollection args) {
return CreateQueryString(Util.NameValueCollectionToDictionary(args));
}
/// <summary>
/// Adds a set of name-value pairs to the end of a given URL
/// as part of the querystring piece. Prefixes a ? or & before
/// first element as necessary.
/// </summary>
/// <param name="builder">The UriBuilder to add arguments to.</param>
/// <param name="args">
/// The arguments to add to the query.
/// If null, <paramref name="builder"/> is not changed.
/// </param>
public static void AppendQueryArgs(UriBuilder builder, IDictionary<string, string> args) {
if (builder == null) {
throw new ArgumentNullException("builder");
}
if (args != null && args.Count > 0) {
StringBuilder sb = new StringBuilder(50 + args.Count * 10);
if (!string.IsNullOrEmpty(builder.Query)) {
sb.Append(builder.Query.Substring(1));
sb.Append('&');
}
sb.Append(CreateQueryString(args));
builder.Query = sb.ToString();
}
}
/// <summary>
/// Equivalent to UriBuilder.ToString() but omits port # if it may be implied.
/// Equivalent to UriBuilder.Uri.ToString(), but doesn't throw an exception if the Host has a wildcard.
/// </summary>
public static string UriBuilderToStringWithImpliedPorts(UriBuilder builder) {
Debug.Assert(builder != null);
// We only check for implied ports on HTTP and HTTPS schemes since those
// are the only ones supported by OpenID anyway.
if ((builder.Port == 80 && string.Equals(builder.Scheme, "http", StringComparison.OrdinalIgnoreCase)) ||
(builder.Port == 443 && string.Equals(builder.Scheme, "https", StringComparison.OrdinalIgnoreCase))) {
// An implied port may be removed.
string url = builder.ToString();
// Be really careful to only remove the first :80 or :443 so we are guaranteed
// we're removing only the port (and not something in the query string that
// looks like a port.
return Regex.Replace(url, @"^(https?://[^:]+):\d+", m => m.Groups[1].Value, RegexOptions.IgnoreCase);
} else {
// The port must be explicitly given anyway.
return builder.ToString();
}
}
}
internal static class Util {
internal const string DefaultNamespace = "DotNetOpenId";
public static string DotNetOpenIdVersion {
get {
string assemblyFullName = Assembly.GetExecutingAssembly().FullName;
bool official = assemblyFullName.Contains("PublicKeyToken=2780ccd10d57b246");
// We use InvariantCulture since this is used for logging.
return string.Format(CultureInfo.InvariantCulture, "{0} ({1})", assemblyFullName, official ? "official" : "private");
}
}
public static IDictionary<string, string> NameValueCollectionToDictionary(NameValueCollection nvc) {
if (nvc == null) return null;
var dict = new Dictionary<string, string>(nvc.Count);
for (int i = 0; i < nvc.Count; i++) {
string key = nvc.GetKey(i);
string value = nvc.Get(i);
// NameValueCollections allow for a null key. Dictionary<TKey, TValue> does not.
// We just skip a null key member. It probably came from a query string that
// started with "?&". See Google Code Issue 81.
if (key != null) {
dict.Add(key, value);
}
}
return dict;
}
public static NameValueCollection DictionaryToNameValueCollection(IDictionary<string, string> dict) {
if (dict == null) return null;
NameValueCollection nvc = new NameValueCollection(dict.Count);
foreach (var pair in dict) {
nvc.Add(pair.Key, pair.Value);
}
return nvc;
}
public static IDictionary<string, string> GetQueryFromContext() {
if (HttpContext.Current == null) throw new InvalidOperationException(Strings.CurrentHttpContextRequired);
var query = HttpContext.Current.Request.RequestType == "GET" ?
HttpContext.Current.Request.QueryString : HttpContext.Current.Request.Form;
return NameValueCollectionToDictionary(query);
}
/// <summary>
/// Gets the original request URL, as seen from the browser before any URL rewrites on the server if any.
/// Cookieless session directory (if applicable) is also included.
/// </summary>
internal static Uri GetRequestUrlFromContext() {
HttpContext context = HttpContext.Current;
if (context == null) throw new InvalidOperationException(Strings.CurrentHttpContextRequired);
// We use Request.Url for the full path to the server, and modify it
// with Request.RawUrl to capture both the cookieless session "directory" if it exists
// and the original path in case URL rewriting is going on. We don't want to be
// fooled by URL rewriting because we're comparing the actual URL with what's in
// the return_to parameter in some cases.
return new Uri(context.Request.Url, context.Request.RawUrl);
// Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless
// session, but not the URL rewriting problem.
}
public static string GetRequiredArg(IDictionary<string, string> query, string key) {
if (query == null) throw new ArgumentNullException("query");
if (key == null) throw new ArgumentNullException("key");
string value;
if (!query.TryGetValue(key, out value) || value.Length == 0)
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.MissingOpenIdQueryParameter, key), query);
return value;
}
public static string GetRequiredArgAllowEmptyValue(IDictionary<string, string> query, string key) {
if (query == null) throw new ArgumentNullException("query");
if (key == null) throw new ArgumentNullException("key");
string value;
if (!query.TryGetValue(key, out value))
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.MissingOpenIdQueryParameter, key), query);
return value;
}
public static string GetOptionalArg(IDictionary<string, string> query, string key) {
if (query == null) throw new ArgumentNullException("query");
if (key == null) throw new ArgumentNullException("key");
string value;
query.TryGetValue(key, out value);
return value;
}
public static byte[] GetRequiredBase64Arg(IDictionary<string, string> query, string key) {
string base64string = GetRequiredArg(query, key);
try {
return Convert.FromBase64String(base64string);
} catch (FormatException) {
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidOpenIdQueryParameterValueBadBase64,
key, base64string), query);
}
}
public static byte[] GetOptionalBase64Arg(IDictionary<string, string> query, string key) {
string base64string = GetOptionalArg(query, key);
if (base64string == null) return null;
try {
return Convert.FromBase64String(base64string);
} catch (FormatException) {
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidOpenIdQueryParameterValueBadBase64,
key, base64string), query);
}
}
public static Identifier GetRequiredIdentifierArg(IDictionary<string, string> query, string key) {
try {
return Util.GetRequiredArg(query, key);
} catch (UriFormatException) {
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidOpenIdQueryParameterValue, key,
Util.GetRequiredArg(query, key), query));
}
}
public static Uri GetRequiredUriArg(IDictionary<string, string> query, string key) {
try {
return new Uri(Util.GetRequiredArg(query, key));
} catch (UriFormatException) {
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidOpenIdQueryParameterValue, key,
Util.GetRequiredArg(query, key), query));
}
}
public static Realm GetOptionalRealmArg(IDictionary<string, string> query, string key) {
try {
string realm = Util.GetOptionalArg(query, key);
// Take care to not return the empty string in case the RP
// sent us realm= but didn't provide a value.
return realm.Length > 0 ? realm : null;
} catch (UriFormatException ex) {
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidOpenIdQueryParameterValue, key,
Util.GetOptionalArg(query, key)), null, query, ex);
}
}
public static bool ArrayEquals<T>(T[] first, T[] second) {
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (first.Length != second.Length) return false;
for (int i = 0; i < first.Length; i++)
if (!first[i].Equals(second[i])) return false;
return true;
}
internal delegate R Func<T, R>(T t);
/// <summary>
/// Scans a list for matches with some element of the OpenID protocol,
/// searching from newest to oldest protocol for the first and best match.
/// </summary>
/// <typeparam name="T">The type of element retrieved from the <see cref="Protocol"/> instance.</typeparam>
/// <param name="elementOf">Takes a <see cref="Protocol"/> instance and returns an element of it.</param>
/// <param name="list">The list to scan for matches.</param>
/// <returns>The protocol with the element that matches some item in the list.</returns>
internal static Protocol FindBestVersion<T>(Func<Protocol, T> elementOf, IEnumerable<T> list) {
foreach (var protocol in Protocol.AllVersions) {
foreach (var item in list) {
if (item != null && item.Equals(elementOf(protocol)))
return protocol;
}
}
return null;
}
/// <summary>
/// Prepares a dictionary for printing as a string.
/// </summary>
/// <remarks>
/// The work isn't done until (and if) the
/// <see cref="Object.ToString"/> method is actually called, which makes it great
/// for logging complex objects without being in a conditional block.
/// </remarks>
internal static object ToString<K, V>(IEnumerable<KeyValuePair<K, V>> pairs) {
return new DelayedToString<IEnumerable<KeyValuePair<K, V>>>(pairs, p => {
var dictionary = pairs as IDictionary<K, V>;
StringBuilder sb = new StringBuilder(dictionary != null ? dictionary.Count * 40 : 200);
foreach (var pair in pairs) {
sb.AppendFormat("\t{0}: {1}{2}", pair.Key, pair.Value, Environment.NewLine);
}
return sb.ToString();
});
}
private class DelayedToString<T> {
public DelayedToString(T obj, Func<T, string> toString) {
this.obj = obj;
this.toString = toString;
}
T obj;
Func<T, string> toString;
public override string ToString() {
return toString(obj);
}
}
}
}
|