wasSharp

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 16  →  ?path2? @ 17
/Web/QValueParsing.cs
@@ -0,0 +1,439 @@
// The MIT License (MIT)
//
// Copyright (c) 2015 Dave Transom
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
// For background refer to this article by Dave Transom
// http://www.singular.co.nz/2008/07/finding-preferred-accept-encoding-header-in-csharp/
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
 
namespace wasSharp.Web
{
/// <summary>
/// Represents a weighted value (or quality value) from an http header e.g. gzip=0.9; deflate; x-gzip=0.5;
/// </summary>
/// <remarks>
/// accept-encoding spec:
/// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
/// </remarks>
/// <example>
/// Accept:
/// text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
/// Accept-Encoding: gzip,deflate
/// Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
/// Accept-Language: en-us,en;q=0.5
/// </example>
[DebuggerDisplay("QValue[{Name}, {Weight}]")]
public struct QValue : IComparable<QValue>
{
private static readonly char[] delimiters = {';', '='};
private const float defaultWeight = 1;
 
#region Fields
 
private float _weight;
private int _ordinal;
 
#endregion
 
#region Constructors
 
/// <summary>
/// Creates a new QValue by parsing the given value
/// for name and weight (qvalue)
/// </summary>
/// <param name="value">The value to be parsed e.g. gzip=0.3</param>
public QValue(string value)
: this(value, 0)
{
}
 
/// <summary>
/// Creates a new QValue by parsing the given value
/// for name and weight (qvalue) and assigns the given
/// ordinal
/// </summary>
/// <param name="value">The value to be parsed e.g. gzip=0.3</param>
/// <param name="ordinal">
/// The ordinal/index where the item
/// was found in the original list.
/// </param>
public QValue(string value, int ordinal)
{
Name = null;
_weight = 0;
_ordinal = ordinal;
 
ParseInternal(ref this, value);
}
 
#endregion
 
#region Properties
 
/// <summary>
/// The name of the value part
/// </summary>
public string Name { get; private set; }
 
/// <summary>
/// The weighting (or qvalue, quality value) of the encoding
/// </summary>
public float Weight => _weight;
 
/// <summary>
/// Whether the value can be accepted
/// i.e. it's weight is greater than zero
/// </summary>
public bool CanAccept => _weight > 0;
 
/// <summary>
/// Whether the value is empty (i.e. has no name)
/// </summary>
public bool IsEmpty => string.IsNullOrEmpty(Name);
 
#endregion
 
#region Methods
 
/// <summary>
/// Parses the given string for name and
/// weigth (qvalue)
/// </summary>
/// <param name="value">The string to parse</param>
public static QValue Parse(string value)
{
var item = new QValue();
ParseInternal(ref item, value);
return item;
}
 
/// <summary>
/// Parses the given string for name and
/// weigth (qvalue)
/// </summary>
/// <param name="value">The string to parse</param>
/// <param name="ordinal">The order of item in sequence</param>
/// <returns></returns>
public static QValue Parse(string value, int ordinal)
{
var item = Parse(value);
item._ordinal = ordinal;
return item;
}
 
/// <summary>
/// Parses the given string for name and
/// weigth (qvalue)
/// </summary>
/// <param name="value">The string to parse</param>
private static void ParseInternal(ref QValue target, string value)
{
var parts = value.Split(delimiters, 3);
if (parts.Length > 0)
{
target.Name = parts[0].Trim();
target._weight = defaultWeight;
}
 
if (parts.Length == 3)
{
float.TryParse(parts[2], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture.NumberFormat,
out target._weight);
}
}
 
#endregion
 
#region IComparable<QValue> Members
 
/// <summary>
/// Compares this instance to another QValue by
/// comparing first weights, then ordinals.
/// </summary>
/// <param name="other">The QValue to compare</param>
/// <returns></returns>
public int CompareTo(QValue other)
{
var value = _weight.CompareTo(other._weight);
if (value == 0)
{
var ord = -_ordinal;
value = ord.CompareTo(-other._ordinal);
}
return value;
}
 
#endregion
 
#region CompareByWeight
 
/// <summary>
/// Compares two QValues in ascending order.
/// </summary>
/// <param name="x">The first QValue</param>
/// <param name="y">The second QValue</param>
/// <returns></returns>
public static int CompareByWeightAsc(QValue x, QValue y)
{
return x.CompareTo(y);
}
 
/// <summary>
/// Compares two QValues in descending order.
/// </summary>
/// <param name="x">The first QValue</param>
/// <param name="y">The second QValue</param>
/// <returns></returns>
public static int CompareByWeightDesc(QValue x, QValue y)
{
return -x.CompareTo(y);
}
 
#endregion
}
 
/// <summary>
/// Provides a collection for working with qvalue http headers
/// </summary>
/// <remarks>
/// accept-encoding spec:
/// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
/// </remarks>
[DebuggerDisplay("QValue[{Count}, {AcceptWildcard}]")]
public sealed class QValueList : List<QValue>
{
private static readonly char[] delimiters = {','};
 
#region Add
 
/// <summary>
/// Adds an item to the list, then applies sorting
/// if AutoSort is enabled.
/// </summary>
/// <param name="item">The item to add</param>
public new void Add(QValue item)
{
base.Add(item);
 
applyAutoSort();
}
 
#endregion
 
#region AddRange
 
/// <summary>
/// Adds a range of items to the list, then applies sorting
/// if AutoSort is enabled.
/// </summary>
/// <param name="collection">The items to add</param>
public new void AddRange(IEnumerable<QValue> collection)
{
var state = AutoSort;
AutoSort = false;
 
base.AddRange(collection);
 
AutoSort = state;
applyAutoSort();
}
 
#endregion
 
#region Find
 
/// <summary>
/// Finds the first QValue with the given name (case-insensitive)
/// </summary>
/// <param name="name">The name of the QValue to search for</param>
/// <returns></returns>
public QValue Find(string name)
{
Predicate<QValue> criteria =
item => item.Name.Equals(name, StringComparison.OrdinalIgnoreCase);
return Find(criteria);
}
 
#endregion
 
#region FindHighestWeight
 
/// <summary>
/// Returns the first match found from the given candidates
/// </summary>
/// <param name="candidates">The list of QValue names to find</param>
/// <returns>The first QValue match to be found</returns>
/// <remarks>
/// Loops from the first item in the list to the last and finds
/// the first candidate - the list must be sorted for weight prior to
/// calling this method.
/// </remarks>
public QValue FindHighestWeight(params string[] candidates)
{
Predicate<QValue> criteria = item => isCandidate(item.Name, candidates);
return Find(criteria);
}
 
#endregion
 
#region FindPreferred
 
/// <summary>
/// Returns the first match found from the given candidates that is accepted
/// </summary>
/// <param name="candidates">The list of names to find</param>
/// <returns>The first QValue match to be found</returns>
/// <remarks>
/// Loops from the first item in the list to the last and finds the
/// first candidate that can be accepted - the list must be sorted for weight
/// prior to calling this method.
/// </remarks>
public QValue FindPreferred(params string[] candidates)
{
Predicate<QValue> criteria =
item => isCandidate(item.Name, candidates) && item.CanAccept;
return Find(criteria);
}
 
#endregion
 
#region DefaultSort
 
/// <summary>
/// Sorts the list comparing by weight in
/// descending order
/// </summary>
public void DefaultSort()
{
Sort(QValue.CompareByWeightDesc);
}
 
#endregion
 
#region applyAutoSort
 
/// <summary>
/// Applies the default sorting method if
/// the autosort field is currently enabled
/// </summary>
private void applyAutoSort()
{
if (AutoSort)
DefaultSort();
}
 
#endregion
 
#region isCandidate
 
/// <summary>
/// Determines if the given item contained within the applied array
/// (case-insensitive)
/// </summary>
/// <param name="item">The string to search for</param>
/// <param name="candidates">The array to search in</param>
/// <returns></returns>
private static bool isCandidate(string item, params string[] candidates)
{
foreach (var candidate in candidates)
{
if (candidate.Equals(item, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
 
#endregion
 
#region Fields
 
#endregion
 
#region Constructors
 
/// <summary>
/// Creates a new instance of an QValueList list from
/// the given string of comma delimited values
/// </summary>
/// <param name="values">The raw string of qvalues to load</param>
public QValueList(string values)
: this(null == values ? new string[0] : values.Split(delimiters, StringSplitOptions.RemoveEmptyEntries))
{
}
 
/// <summary>
/// Creates a new instance of an QValueList from
/// the given string array of qvalues
/// </summary>
/// <param name="values">
/// The array of qvalue strings
/// i.e. name(;q=[0-9\.]+)?
/// </param>
/// <remarks>
/// Should AcceptWildcard include */* as well?
/// What about other wildcard forms?
/// </remarks>
public QValueList(string[] values)
{
var ordinal = -1;
foreach (var value in values)
{
var qvalue = QValue.Parse(value.Trim(), ++ordinal);
if (qvalue.Name.Equals("*")) // wildcard
AcceptWildcard = qvalue.CanAccept;
Add(qvalue);
}
 
/// this list should be sorted by weight for
/// methods like FindPreferred to work correctly
DefaultSort();
AutoSort = true;
}
 
#endregion
 
#region Properties
 
/// <summary>
/// Whether or not the wildcarded encoding is available and allowed
/// </summary>
public bool AcceptWildcard { get; }
 
/// <summary>
/// Whether, after an add operation, the list should be resorted
/// </summary>
public bool AutoSort { get; set; }
 
/// <summary>
/// Synonym for FindPreferred
/// </summary>
/// <param name="candidates">The preferred order in which to return an encoding</param>
/// <returns>An QValue based on weight, or null</returns>
public QValue this[params string[] candidates] => FindPreferred(candidates);
 
#endregion
}
}
/Web/wasHTTPClient.cs
@@ -11,6 +11,7 @@
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using wasSharp.Collections.Utilities;
 
namespace wasSharp.Web
{
@@ -62,13 +63,29 @@
{
HTTPClient.DefaultRequestHeaders.Authorization = authentication;
}
if (headers != null)
// Add some standard headers:
// Accept - for socially acceptable security of mod_sec
// Accept-Encoding - since we want to use compression if possible
switch (headers != null)
{
foreach (var header in headers)
{
HTTPClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
case false:
headers = new Dictionary<string, string> {{"Accept", @"*/*"}, {"Accept-Encoding", "gzip,defalate"}};
break;
default:
if (!headers.ContainsKey("Accept"))
{
headers.Add("Accept", @"*/*");
}
if (!headers.ContainsKey("Accept-Encoding"))
{
headers.Add("Accept-Encoding", "gzip,deflate");
}
break;
}
foreach (var header in headers)
{
HTTPClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
HTTPClient.Timeout = TimeSpan.FromMilliseconds(timeout);
MediaType = mediaType;
}
@@ -100,6 +117,17 @@
{
request.Headers.Add(header.Key, header.Value);
}
// Add some standard headers:
// Accept - for socially acceptable security of mod_sec
// Accept-Encoding - since we want to use compression if possible
if (!headers.ContainsKey("Accept"))
{
headers.Add("Accept", @"*/*");
}
if (!headers.ContainsKey("Accept-Encoding"))
{
headers.Add("Accept-Encoding", "gzip,deflate");
}
using (var response = await HTTPClient.SendAsync(request))
{
return response.IsSuccessStatusCode
@@ -143,6 +171,17 @@
{
request.Headers.Add(header.Key, header.Value);
}
// Add some standard headers:
// Accept - for socially acceptable security of mod_sec
// Accept-Encoding - since we want to use compression if possible
if (!headers.ContainsKey("Accept"))
{
headers.Add("Accept", @"*/*");
}
if (!headers.ContainsKey("Accept-Encoding"))
{
headers.Add("Accept-Encoding", "gzip,deflate");
}
using (var response = await HTTPClient.SendAsync(request))
{
return response.IsSuccessStatusCode
@@ -292,8 +331,7 @@
 
public void Dispose()
{
if (HTTPClient != null)
HTTPClient.Dispose();
HTTPClient?.Dispose();
}
}
}