using System;
using System.Collections.Specialized;
using System.Net;
using EmbedIO.Internal;
namespace EmbedIO.Utilities
{
///
/// Parses URL queries or URL-encoded HTML forms.
///
public static class UrlEncodedDataParser
{
///
/// Parses a URL query or URL-encoded HTML form.
/// Unlike , the returned
/// will have bracketed indexes stripped away;
/// for example, a[0]=1&a[1]=2 will yield the same result as a=1&a=2,
/// i.e. a with one key (a) associated with
/// two values (1 and 2).
///
/// The string to parse.
/// If this parameter is ,
/// tokens not followed by an equal sign (e.g. this in a=1&this&b=2)
/// will be grouped as values of a null key.
/// This is the same behavior as the and
/// properties.
/// If this parameter is , tokens not followed by an equal sign
/// (e.g. this in a=1&this&b=2) will be considered keys with an empty
/// value. This is the same behavior as the
/// extension method.
/// (the default) to return
/// a mutable (non-read-only) collection; to return a read-only collection.
/// A containing the parsed data.
public static NameValueCollection Parse(string source, bool groupFlags, bool mutableResult = true)
{
var result = new LockableNameValueCollection();
// Verify there is data to parse; otherwise, return an empty collection.
if (string.IsNullOrEmpty(source))
{
if (!mutableResult)
result.MakeReadOnly();
return result;
}
void AddKeyValuePair(string? key, string value)
{
if (key != null)
{
// Decode the key.
key = WebUtility.UrlDecode(key);
// Discard bracketed index (used e.g. by PHP)
var bracketPos = key.IndexOf("[", StringComparison.Ordinal);
if (bracketPos > 0)
key = key.Substring(0, bracketPos);
}
// Decode the value.
value = WebUtility.UrlDecode(value);
// Add the KVP to the collection.
result.Add(key, value);
}
// Skip the initial question mark,
// in case source is the Query property of a Uri.
var kvpPos = source[0] == '?' ? 1 : 0;
var length = source.Length;
while (kvpPos < length)
{
var separatorPos = kvpPos;
var equalPos = -1;
while (separatorPos < length)
{
var c = source[separatorPos];
if (c == '&')
break;
if (c == '=' && equalPos < 0)
equalPos = separatorPos;
separatorPos++;
}
// Split by the equals char into key and value.
// Some KVPS will have only their key, some will have both key and value
// Some other might be repeated which really means an array
if (equalPos < 0)
{
if (groupFlags)
{
AddKeyValuePair(null, source.Substring(kvpPos, separatorPos - kvpPos));
}
else
{
AddKeyValuePair(source.Substring(kvpPos, separatorPos - kvpPos), string.Empty);
}
}
else
{
AddKeyValuePair(
source.Substring(kvpPos, equalPos - kvpPos),
source.Substring(equalPos + 1, separatorPos - equalPos - 1));
}
// Edge case: if the last character in source is '&',
// there's an empty KVP that we would otherwise skip.
if (separatorPos == length - 1)
{
AddKeyValuePair(groupFlags ? null : string.Empty, string.Empty);
break;
}
// On to next KVP
kvpPos = separatorPos + 1;
}
if (!mutableResult)
result.MakeReadOnly();
return result;
}
}
}