using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Globalization;
namespace DotNetOpenId {
///
/// Indicates the level of strictness to require when decoding a
/// Key-Value Form encoded dictionary.
///
public enum KeyValueFormConformanceLevel {
///
/// Be as forgiving as possible to errors made while encoding.
///
Loose,
///
/// Allow for certain errors in encoding attributable to ambiguities
/// in the OpenID 1.1 spec's description of the encoding.
///
OpenId11,
///
/// The strictest mode. The decoder requires the encoded dictionary
/// to be in strict compliance with OpenID 2.0's description of
/// the encoding.
///
OpenId20,
}
///
/// 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
///
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; }
///
/// 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.
///
/// The UTF8 byte array.
///
/// 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.
///
public byte[] GetBytes(IDictionary dictionary) {
string[] keys = new string[dictionary.Count];
dictionary.Keys.CopyTo(keys, 0);
return GetBytes(dictionary, keys);
}
///
/// Encodes key/value pairs to Key-Value Form.
///
///
/// The dictionary of key/value pairs to convert to a byte stream.
///
///
/// The order in which to encode the key/value pairs.
/// Useful in scenarios where a byte[] must be exactly reproduced.
///
/// The UTF8 byte array.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
public byte[] GetBytes(IDictionary dictionary, IList 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();
}
///
/// Decodes bytes in Key-Value Form to key/value pairs.
///
/// The stream of Key-Value Form encoded bytes.
/// The deserialized dictionary.
public IDictionary GetDictionary(Stream data) {
using (StreamReader reader = new StreamReader(data, textEncoding)) {
var dict = new Dictionary();
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;
}
}
}
}