using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace gehGassi.Pwned
{
public static class QueryHelpers
{
///
/// Append the given query key and value to the URI.
///
/// The base URI.
/// The name of the query key.
/// The query value.
/// The combined result.
public static string AddQueryString(string uri, string name, string value)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
return AddQueryString(
uri, new[] { new KeyValuePair(name, value) });
}
///
/// Append the given query keys and values to the uri.
///
/// The base uri.
/// A collection of name value query pairs to append.
/// The combined result.
public static string AddQueryString(string uri, IDictionary queryString)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}
if (queryString == null)
{
throw new ArgumentNullException(nameof(queryString));
}
return AddQueryString(uri, (IEnumerable>)queryString);
}
private static string AddQueryString(
string uri,
IEnumerable> queryString)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}
if (queryString == null)
{
throw new ArgumentNullException(nameof(queryString));
}
var anchorIndex = uri.IndexOf('#');
var uriToBeAppended = uri;
var anchorText = "";
// If there is an anchor, then the query string must be inserted before its first occurence.
if (anchorIndex != -1)
{
anchorText = uri.Substring(anchorIndex);
uriToBeAppended = uri.Substring(0, anchorIndex);
}
var queryIndex = uriToBeAppended.IndexOf('?');
var hasQuery = queryIndex != -1;
var sb = new StringBuilder();
sb.Append(uriToBeAppended);
foreach (var parameter in queryString)
{
sb.Append(hasQuery ? '&' : '?');
sb.Append(UrlEncoder.Default.Encode(parameter.Key));
sb.Append('=');
sb.Append(UrlEncoder.Default.Encode(parameter.Value));
hasQuery = true;
}
sb.Append(anchorText);
return sb.ToString();
}
///
/// Parse a query string into its component key and value parts.
///
/// The raw query string value, with or without the leading '?'.
/// A collection of parsed keys and values.
public static Dictionary ParseQuery(string queryString)
{
var result = ParseNullableQuery(queryString);
if (result == null)
{
return new Dictionary();
}
return result;
}
///
/// Parse a query string into its component key and value parts.
///
/// The raw query string value, with or without the leading '?'.
/// A collection of parsed keys and values, null if there are no entries.
public static Dictionary ParseNullableQuery(string queryString)
{
var accumulator = new KeyValueAccumulator();
if (string.IsNullOrEmpty(queryString) || queryString == "?")
{
return null;
}
int scanIndex = 0;
if (queryString[0] == '?')
{
scanIndex = 1;
}
int textLength = queryString.Length;
int equalIndex = queryString.IndexOf('=');
if (equalIndex == -1)
{
equalIndex = textLength;
}
while (scanIndex < textLength)
{
int delimiterIndex = queryString.IndexOf('&', scanIndex);
if (delimiterIndex == -1)
{
delimiterIndex = textLength;
}
if (equalIndex < delimiterIndex)
{
while (scanIndex != equalIndex && char.IsWhiteSpace(queryString[scanIndex]))
{
++scanIndex;
}
string name = queryString.Substring(scanIndex, equalIndex - scanIndex);
string value = queryString.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1);
accumulator.Append(
Uri.UnescapeDataString(name.Replace('+', ' ')),
Uri.UnescapeDataString(value.Replace('+', ' ')));
equalIndex = queryString.IndexOf('=', delimiterIndex);
if (equalIndex == -1)
{
equalIndex = textLength;
}
}
else
{
if (delimiterIndex > scanIndex)
{
accumulator.Append(queryString.Substring(scanIndex, delimiterIndex - scanIndex), string.Empty);
}
}
scanIndex = delimiterIndex + 1;
}
if (!accumulator.HasValues)
{
return null;
}
return accumulator.GetResults();
}
}
public struct KeyValueAccumulator
{
private Dictionary _accumulator;
private Dictionary> _expandingAccumulator;
public void Append(string key, string value)
{
if (_accumulator == null)
{
_accumulator = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
StringValues values;
if (_accumulator.TryGetValue(key, out values))
{
if (values.Count == 0)
{
// Marker entry for this key to indicate entry already in expanding list dictionary
_expandingAccumulator[key].Add(value);
}
else if (values.Count == 1)
{
// Second value for this key
_accumulator[key] = new string[] { values[0], value };
}
else
{
// Third value for this key
// Add zero count entry and move to data to expanding list dictionary
_accumulator[key] = default(StringValues);
if (_expandingAccumulator == null)
{
_expandingAccumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase);
}
// Already 3 entries so use starting allocated as 8; then use List's expansion mechanism for more
var list = new List(8);
var array = values.ToArray();
list.Add(array[0]);
list.Add(array[1]);
list.Add(value);
_expandingAccumulator[key] = list;
}
}
else
{
// First value for this key
_accumulator[key] = new StringValues(value);
}
ValueCount++;
}
public bool HasValues => ValueCount > 0;
public int KeyCount => _accumulator?.Count ?? 0;
public int ValueCount { get; private set; }
public Dictionary GetResults()
{
if (_expandingAccumulator != null)
{
// Coalesce count 3+ multi-value entries into _accumulator dictionary
foreach (var entry in _expandingAccumulator)
{
_accumulator[entry.Key] = new StringValues(entry.Value.ToArray());
}
}
return _accumulator ?? new Dictionary(0, StringComparer.OrdinalIgnoreCase);
}
}
}