// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
#if !NETFX_CORE
using System.Net.Http.Formatting.Internal;
#endif
using System.Net.Http.Formatting.Parsers;
using System.Text;
using System.Threading;
using System.Web.Http;
#if NETFX_CORE
using NameValueCollection = System.Net.Http.Formatting.HttpValueCollection;
#endif
namespace System.Net.Http.Formatting
{
///
/// Represent the form data.
/// - This has 100% fidelity (including ordering, which is important for deserializing ordered array).
/// - using interfaces allows us to optimize the implementation. E.g., we can avoid eagerly string-splitting a 10gb file.
/// - This also provides a convenient place to put extension methods.
///
#if NETFX_CORE
internal
#else
public
#endif
class FormDataCollection : IEnumerable>
{
private readonly IEnumerable> _pairs;
private NameValueCollection _nameValueCollection;
///
/// Initialize a form collection around incoming data.
/// The key value enumeration should be immutable.
///
/// incoming set of key value pairs. Ordering is preserved.
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is the convention for representing FormData")]
public FormDataCollection(IEnumerable> pairs)
{
if (pairs == null)
{
throw Error.ArgumentNull("pairs");
}
_pairs = pairs;
}
///
/// Initialize a form collection from a query string.
/// Uri and FormURl body have the same schema.
///
public FormDataCollection(Uri uri)
{
if (uri == null)
{
throw Error.ArgumentNull("uri");
}
string query = uri.Query;
if (query != null && query.Length > 0 && query[0] == '?')
{
query = query.Substring(1);
}
_pairs = ParseQueryString(query);
}
///
/// Initialize a form collection from a URL encoded query string. Any leading question
/// mark (?) will be considered part of the query string and treated as any other value.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "string is a query string, not a URI")]
public FormDataCollection(string query)
{
_pairs = ParseQueryString(query);
}
///
/// Gets values associated with a given key. If there are multiple values, they're concatenated.
///
/// The name of the entry that contains the values to get. The name can be null.
/// Values associated with a given key. If there are multiple values, they're concatenated.
public string this[string name]
{
get
{
return Get(name);
}
}
// Helper to invoke parser around a query string
private static IEnumerable> ParseQueryString(string query)
{
List> result = new List>();
if (String.IsNullOrWhiteSpace(query))
{
return result;
}
byte[] bytes = Encoding.UTF8.GetBytes(query);
FormUrlEncodedParser parser = new FormUrlEncodedParser(result, Int64.MaxValue);
int bytesConsumed = 0;
ParserState state = parser.ParseBuffer(bytes, bytes.Length, ref bytesConsumed, isFinal: true);
if (state != ParserState.Done)
{
throw Error.InvalidOperation(Properties.Resources.FormUrlEncodedParseError, bytesConsumed);
}
return result;
}
///
/// Get the collection as a NameValueCollection.
/// Beware this loses some ordering. Values are ordered within a key,
/// but keys are no longer ordered against each other.
///
public NameValueCollection ReadAsNameValueCollection()
{
if (_nameValueCollection == null)
{
// Initialize in a private collection to be thread-safe, and swap the finished object.
// Ok to double initialize this.
HttpValueCollection newCollection = HttpValueCollection.Create(this);
Interlocked.Exchange(ref _nameValueCollection, newCollection);
}
return _nameValueCollection;
}
///
/// Get values associated with a given key. If there are multiple values, they're concatenated.
///
public string Get(string key)
{
return ReadAsNameValueCollection().Get(key);
}
///
/// Get a value associated with a given key.
///
public string[] GetValues(string key)
{
return ReadAsNameValueCollection().GetValues(key);
}
public IEnumerator> GetEnumerator()
{
return _pairs.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
IEnumerable ie = _pairs;
return ie.GetEnumerator();
}
}
}