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
|
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Globalization;
namespace DotNetOpenId {
/// <summary>
/// Indicates the level of strictness to require when decoding a
/// Key-Value Form encoded dictionary.
/// </summary>
public enum KeyValueFormConformanceLevel {
/// <summary>
/// Be as forgiving as possible to errors made while encoding.
/// </summary>
Loose,
/// <summary>
/// Allow for certain errors in encoding attributable to ambiguities
/// in the OpenID 1.1 spec's description of the encoding.
/// </summary>
OpenId11,
/// <summary>
/// The strictest mode. The decoder requires the encoded dictionary
/// to be in strict compliance with OpenID 2.0's description of
/// the encoding.
/// </summary>
OpenId20,
}
/// <summary>
/// Conversion to and from the Key-Value Form Encoding defined by
/// OpenID Authentication 2.0 section 4.1.1.
/// http://openid.net/specs/openid-authentication-2_0.html#anchor4
/// </summary>
internal class KeyValueFormEncoding : IProtocolMessageEncoding {
static readonly char[] illegalKeyCharacters = { '\n', ':' };
static readonly char[] illegalValueCharacters = { '\n' };
const string newLineCharacters = "\n";
static readonly Encoding textEncoding = new UTF8Encoding(false);
public KeyValueFormEncoding() {
ConformanceLevel = KeyValueFormConformanceLevel.Loose;
}
public KeyValueFormEncoding(KeyValueFormConformanceLevel conformanceLevel) {
ConformanceLevel = conformanceLevel;
}
public KeyValueFormConformanceLevel ConformanceLevel { get; private set; }
/// <summary>
/// Encodes key/value pairs to Key-Value Form.
/// Do not use for dictionaries of signed fields! Instead use the overload
/// that accepts a list of in-order keys.
/// </summary>
/// <returns>The UTF8 byte array.</returns>
/// <remarks>
/// Because dictionaries do not guarantee ordering,
/// encoding a dictionary without an explicitly given key order
/// is useless in OpenID scenarios where a signature must match.
/// </remarks>
public byte[] GetBytes(IDictionary<string, string> dictionary) {
string[] keys = new string[dictionary.Count];
dictionary.Keys.CopyTo(keys, 0);
return GetBytes(dictionary, keys);
}
/// <summary>
/// Encodes key/value pairs to Key-Value Form.
/// </summary>
/// <param name="dictionary">
/// The dictionary of key/value pairs to convert to a byte stream.
/// </param>
/// <param name="keyOrder">
/// The order in which to encode the key/value pairs.
/// Useful in scenarios where a byte[] must be exactly reproduced.
/// </param>
/// <returns>The UTF8 byte array.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
public byte[] GetBytes(IDictionary<string, string> dictionary, IList<string> keyOrder) {
if (dictionary == null) throw new ArgumentNullException("dictionary");
if (keyOrder == null) throw new ArgumentNullException("keyOrder");
if (dictionary.Count != keyOrder.Count) throw new ArgumentException(Strings.KeysListAndDictionaryDoNotMatch);
MemoryStream ms = new MemoryStream();
using (StreamWriter sw = new StreamWriter(ms, textEncoding)) {
sw.NewLine = newLineCharacters;
foreach (string keyInOrder in keyOrder) {
string key = keyInOrder.Trim();
string value = dictionary[key].Trim();
if (key.IndexOfAny(illegalKeyCharacters) >= 0)
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidCharacterInKeyValueFormInput, key));
if (value.IndexOfAny(illegalValueCharacters) >= 0)
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidCharacterInKeyValueFormInput, value));
sw.Write(key);
sw.Write(':');
sw.Write(value);
sw.WriteLine();
}
}
return ms.ToArray();
}
/// <summary>
/// Decodes bytes in Key-Value Form to key/value pairs.
/// </summary>
/// <param name="data">The stream of Key-Value Form encoded bytes.</param>
/// <returns>The deserialized dictionary.</returns>
public IDictionary<string, string> GetDictionary(Stream data) {
using (StreamReader reader = new StreamReader(data, textEncoding)) {
var dict = new Dictionary<string, string>();
int line_num = 0;
string line;
while ((line = reader.ReadLine()) != null) {
line_num++;
if (ConformanceLevel == KeyValueFormConformanceLevel.Loose) {
line = line.Trim();
if (line.Length == 0) continue;
}
string[] parts = line.Split(new[] { ':' }, 2);
if (parts.Length != 2) {
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidKeyValueFormCharacterMissing, ':', line_num, line));
}
if (ConformanceLevel > KeyValueFormConformanceLevel.Loose) {
if (char.IsWhiteSpace(parts[0], parts[0].Length-1) ||
char.IsWhiteSpace(parts[1], 0)) {
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidCharacterInKeyValueFormInput, ' ', line_num, line));
}
}
if (ConformanceLevel < KeyValueFormConformanceLevel.OpenId20) {
parts[0] = parts[0].Trim();
parts[1] = parts[1].Trim();
}
// calling Add method will throw if a key is encountered twice,
// which we should do.
dict.Add(parts[0], parts[1]);
}
if (ConformanceLevel > KeyValueFormConformanceLevel.Loose) {
reader.BaseStream.Seek(-1, SeekOrigin.End);
if (reader.BaseStream.ReadByte() != '\n') {
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Strings.InvalidKeyValueFormCharacterMissing, "\\n", line_num, line));
}
}
return dict;
}
}
}
}
|