Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / ndp / fx / src / DataWeb / Server / System / Data / Services / HttpProcessUtility.cs / 1 / HttpProcessUtility.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides a utility class to help in processing HTTP requests.
//
//
// @owner [....]
//---------------------------------------------------------------------
#if ASTORIA_CLIENT
namespace System.Data.Services.Client
#else
namespace System.Data.Services
#endif
{
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
/// Provides helper methods for processing HTTP requests.
internal static class HttpProcessUtility
{
/// UTF-8 encoding, without the BOM preamble.
///
/// While a BOM preamble on UTF8 is generally benign, it seems that some MIME handlers under IE6 will not
/// process the payload correctly when included.
///
/// Because the data service should include the encoding as part of the Content-Type in the response,
/// there should be no ambiguity as to what encoding is being used.
///
/// For further information, see http://www.unicode.org/faq/utf_bom.html#BOM.
///
internal static readonly UTF8Encoding EncodingUtf8NoPreamble = new UTF8Encoding(false, true);
/// Encoding to fall back to an appropriate encoding is not available.
internal static Encoding FallbackEncoding
{
get
{
return EncodingUtf8NoPreamble;
}
}
/// Encoding implied by an unspecified encoding value.
/// See http://tools.ietf.org/html/rfc2616#section-3.4.1 for details.
private static Encoding MissingEncoding
{
get
{
#if ASTORIA_LIGHT // ISO-8859-1 not available
return Encoding.UTF8;
#else
return Encoding.GetEncoding("ISO-8859-1", new EncoderExceptionFallback(), new DecoderExceptionFallback());
#endif
}
}
#if !ASTORIA_CLIENT
/// Builds a Content-Type header which includes MIME type and encoding information.
/// MIME type to be used.
/// Encoding to be used in response, possibly null.
/// The value for the Content-Type header.
internal static string BuildContentType(string mime, Encoding encoding)
{
Debug.Assert(mime != null, "mime != null");
if (encoding == null)
{
return mime;
}
else
{
return mime + ";charset=" + encoding.WebName;
}
}
/// Selects an acceptable MIME type that satisfies the Accepts header.
/// Text for Accepts header.
///
/// Types that the server is willing to return, in descending order
/// of preference.
///
/// The best MIME type for the client
internal static string SelectMimeType(string acceptTypesText, string[] availableTypes)
{
Debug.Assert(availableTypes != null, "acceptableTypes != null");
string selectedContentType = null;
int selectedMatchingParts = -1;
int selectedQualityValue = 0;
int selectedPreferenceIndex = Int32.MaxValue;
bool acceptable = false;
bool acceptTypesEmpty = true;
if (!String.IsNullOrEmpty(acceptTypesText))
{
IEnumerable acceptTypes = MimeTypesFromAcceptHeader(acceptTypesText);
foreach (MediaType acceptType in acceptTypes)
{
acceptTypesEmpty = false;
for (int i = 0; i < availableTypes.Length; i++)
{
string availableType = availableTypes[i];
int matchingParts = acceptType.GetMatchingParts(availableType);
if (matchingParts < 0)
{
continue;
}
if (matchingParts > selectedMatchingParts)
{
// A more specific type wins.
selectedContentType = availableType;
selectedMatchingParts = matchingParts;
selectedQualityValue = acceptType.SelectQualityValue();
selectedPreferenceIndex = i;
acceptable = selectedQualityValue != 0;
}
else if (matchingParts == selectedMatchingParts)
{
// A type with a higher q-value wins.
int candidateQualityValue = acceptType.SelectQualityValue();
if (candidateQualityValue > selectedQualityValue)
{
selectedContentType = availableType;
selectedQualityValue = candidateQualityValue;
selectedPreferenceIndex = i;
acceptable = selectedQualityValue != 0;
}
else if (candidateQualityValue == selectedQualityValue)
{
// A type that is earlier in the availableTypes array wins.
if (i < selectedPreferenceIndex)
{
selectedContentType = availableType;
selectedPreferenceIndex = i;
}
}
}
}
}
}
if (acceptTypesEmpty)
{
selectedContentType = availableTypes[0];
}
else if (!acceptable)
{
selectedContentType = null;
}
return selectedContentType;
}
/// Gets the appropriate MIME type for the request, throwing if there is none.
/// Text as it appears in an HTTP Accepts header.
/// Preferred content type to match if an exact media type is given - this is in descending order of preference.
/// Preferred fallback content type for inexact matches.
/// One of exactContentType or inexactContentType.
internal static string SelectRequiredMimeType(
string acceptTypesText,
string[] exactContentType,
string inexactContentType)
{
Debug.Assert(exactContentType != null && exactContentType.Length != 0, "exactContentType != null && exactContentType.Length != 0");
Debug.Assert(inexactContentType != null, "inexactContentType != null");
string selectedContentType = null;
int selectedMatchingParts = -1;
int selectedQualityValue = 0;
bool acceptable = false;
bool acceptTypesEmpty = true;
bool foundExactMatch = false;
if (!String.IsNullOrEmpty(acceptTypesText))
{
IEnumerable acceptTypes = MimeTypesFromAcceptHeader(acceptTypesText);
foreach (MediaType acceptType in acceptTypes)
{
acceptTypesEmpty = false;
for (int i = 0; i < exactContentType.Length; i++)
{
if (acceptType.MimeType == exactContentType[i])
{
selectedContentType = exactContentType[i];
selectedQualityValue = acceptType.SelectQualityValue();
acceptable = selectedQualityValue != 0;
foundExactMatch = true;
break;
}
}
if (foundExactMatch)
{
break;
}
int matchingParts = acceptType.GetMatchingParts(inexactContentType);
if (matchingParts < 0)
{
continue;
}
if (matchingParts > selectedMatchingParts)
{
// A more specific type wins.
selectedContentType = inexactContentType;
selectedMatchingParts = matchingParts;
selectedQualityValue = acceptType.SelectQualityValue();
acceptable = selectedQualityValue != 0;
}
else if (matchingParts == selectedMatchingParts)
{
// A type with a higher q-value wins.
int candidateQualityValue = acceptType.SelectQualityValue();
if (candidateQualityValue > selectedQualityValue)
{
selectedContentType = inexactContentType;
selectedQualityValue = candidateQualityValue;
acceptable = selectedQualityValue != 0;
}
}
}
}
if (!acceptable && !acceptTypesEmpty)
{
throw Error.HttpHeaderFailure(415, Strings.DataServiceException_UnsupportedMediaType);
}
if (acceptTypesEmpty)
{
Debug.Assert(selectedContentType == null, "selectedContentType == null - otherwise accept types were not empty");
selectedContentType = inexactContentType;
}
Debug.Assert(selectedContentType != null, "selectedContentType != null - otherwise no selection was made");
return selectedContentType;
}
/// Gets the best encoding available for the specified charset request.
///
/// The Accept-Charset header value (eg: "iso-8859-5, unicode-1-1;q=0.8").
///
/// An Encoding object appropriate to the specifed charset request.
internal static Encoding EncodingFromAcceptCharset(string acceptCharset)
{
// Determines the appropriate encoding mapping according to
// RFC 2616.14.2 (http://tools.ietf.org/html/rfc2616#section-14.2).
Encoding result = null;
if (!string.IsNullOrEmpty(acceptCharset))
{
// PERF: keep a cache of original strings to resolved Encoding.
List parts = new List(AcceptCharsetParts(acceptCharset));
parts.Sort(delegate(CharsetPart x, CharsetPart y)
{
return y.Quality - x.Quality;
});
var encoderFallback = new EncoderExceptionFallback();
var decoderFallback = new DecoderExceptionFallback();
foreach (CharsetPart part in parts)
{
if (part.Quality > 0)
{
// When UTF-8 is specified, select the version that doesn't use the BOM.
if (String.Compare("utf-8", part.Charset, StringComparison.OrdinalIgnoreCase) == 0)
{
result = FallbackEncoding;
break;
}
else
{
try
{
result = Encoding.GetEncoding(part.Charset, encoderFallback, decoderFallback);
break;
}
catch (ArgumentException)
{
// This exception is thrown when the character
// set isn't supported - it is ignored so
// other possible charsets are evaluated.
}
}
}
}
}
// No Charset was specifed, or if charsets were specified, no valid charset was found.
// Returning a different charset is also valid.
if (result == null)
{
result = FallbackEncoding;
}
return result;
}
#endif
/// Reads a Content-Type header and extracts the MIME type/subtype and encoding.
/// The Content-Type header.
/// The MIME type in standard type/subtype form, without parameters.
/// Encoding (possibly null).
/// parameters of content type
internal static KeyValuePair[] ReadContentType(string contentType, out string mime, out Encoding encoding)
{
if (String.IsNullOrEmpty(contentType))
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_ContentTypeMissing);
}
MediaType mediaType = ReadMediaType(contentType);
mime = mediaType.MimeType;
encoding = mediaType.SelectEncoding();
return mediaType.Parameters;
}
#if !ASTORIA_CLIENT
///
/// Given the parameters, search for the parameter with the given name and returns its value.
///
/// list of parameters specified.
/// name of the parameter whose value needs to be returned.
/// returns the value of the parameter with the given name. Returns null, if the parameter is not found.
internal static string GetParameterValue(KeyValuePair[] parameters, string parameterName)
{
if (parameters == null)
{
return null;
}
foreach (KeyValuePair parameterInfo in parameters)
{
if (parameterInfo.Key == parameterName)
{
return parameterInfo.Value;
}
}
return null;
}
#endif
/// Tries to read an ADO.NET Data Service version string.
/// Text to read.
/// Parsed version and trailing text.
/// true if the version was read successfully; false otherwise.
internal static bool TryReadVersion(string text, out KeyValuePair result)
{
Debug.Assert(text != null, "text != null");
// Separate version number and extra string.
int separator = text.IndexOf(';');
string versionText, libraryName;
if (separator >= 0)
{
versionText = text.Substring(0, separator);
libraryName = text.Substring(separator + 1).Trim();
}
else
{
versionText = text;
libraryName = null;
}
result = default(KeyValuePair);
versionText = versionText.Trim();
// The Version constructor allows for a more complex syntax, including
// build, revisions, and major/minor for revisions. We only take two
// number parts separated by a single dot.
bool dotFound = false;
for (int i = 0; i < versionText.Length; i++)
{
if (versionText[i] == '.')
{
if (dotFound)
{
return false;
}
dotFound = true;
}
else if (versionText[i] < '0' || versionText[i] > '9')
{
return false;
}
}
try
{
result = new KeyValuePair(new Version(versionText), libraryName);
return true;
}
catch (Exception e)
{
if (e is FormatException || e is OverflowException || e is ArgumentException)
{
return false;
}
throw;
}
}
/// Gets the named encoding if specified.
/// Name (possibly null or empty).
///
/// The named encoding if specified; the encoding for HTTP missing
/// charset specification otherwise.
///
///
/// See http://tools.ietf.org/html/rfc2616#section-3.4.1 for details.
///
private static Encoding EncodingFromName(string name)
{
if (name == null)
{
return MissingEncoding;
}
name = name.Trim();
if (name.Length == 0)
{
return MissingEncoding;
}
else
{
try
{
return Encoding.GetEncoding(name);
}
catch (ArgumentException)
{
// 400 - Bad Request
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EncodingNotSupported(name));
}
}
}
#if !ASTORIA_CLIENT
/// Creates a new exception for parsing errors.
/// Message for error.
/// A new exception that can be thrown for a parsing error.
private static DataServiceException CreateParsingException(string message)
{
// Status code "400" ; Section 10.4.1: Bad Request
return Error.HttpHeaderFailure(400, message);
}
#endif
/// Reads the type and subtype specifications for a MIME type.
/// Text in which specification exists.
/// Pointer into text.
/// Type of media found.
/// Subtype of media found.
private static void ReadMediaTypeAndSubtype(string text, ref int textIndex, out string type, out string subType)
{
Debug.Assert(text != null, "text != null");
int textStart = textIndex;
if (ReadToken(text, ref textIndex))
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeUnspecified);
}
if (text[textIndex] != '/')
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSlash);
}
type = text.Substring(textStart, textIndex - textStart);
textIndex++;
int subTypeStart = textIndex;
ReadToken(text, ref textIndex);
if (textIndex == subTypeStart)
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSubType);
}
subType = text.Substring(subTypeStart, textIndex - subTypeStart);
}
/// Reads a media type definition as used in a Content-Type header.
/// Text to read.
/// The defined by the specified
/// All syntactic errors will produce a 400 - Bad Request status code.
private static MediaType ReadMediaType(string text)
{
Debug.Assert(text != null, "text != null");
string type;
string subType;
int textIndex = 0;
ReadMediaTypeAndSubtype(text, ref textIndex, out type, out subType);
KeyValuePair[] parameters = null;
while (!SkipWhitespace(text, ref textIndex))
{
if (text[textIndex] != ';')
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter);
}
textIndex++;
if (SkipWhitespace(text, ref textIndex))
{
// ';' should be a leading separator, but we choose to be a
// bit permissive and allow it as a final delimiter as well.
break;
}
ReadMediaTypeParameter(text, ref textIndex, ref parameters);
}
return new MediaType(type, subType, parameters);
}
///
/// Reads a token on the specified text by advancing an index on it.
///
/// Text to read token from.
/// Index for the position being scanned on text.
/// true if the end of the text was reached; false otherwise.
private static bool ReadToken(string text, ref int textIndex)
{
while (textIndex < text.Length && IsHttpToken(text[textIndex]))
{
textIndex++;
}
return (textIndex == text.Length);
}
///
/// Skips whitespace in the specified text by advancing an index to
/// the next non-whitespace character.
///
/// Text to scan.
/// Index to begin scanning from.
/// true if the end of the string was reached, false otherwise.
private static bool SkipWhitespace(string text, ref int textIndex)
{
Debug.Assert(text != null, "text != null");
Debug.Assert(text.Length >= 0, "text >= 0");
Debug.Assert(textIndex <= text.Length, "text <= text.Length");
while (textIndex < text.Length && Char.IsWhiteSpace(text, textIndex))
{
textIndex++;
}
return (textIndex == text.Length);
}
#if !ASTORIA_CLIENT
///
/// Verfies whether the specified character is a valid separator in
/// an HTTP header list of element.
///
/// Character to verify.
/// true if c is a valid character for separating elements; false otherwise.
private static bool IsHttpElementSeparator(char c)
{
return c == ',' || c == ' ' || c == '\t';
}
///
/// "Reads" a literal from the specified string by verifying that
/// the exact text can be found at the specified position.
///
/// Text within which a literal should be checked.
/// Index in text where the literal should be found.
/// Literal to check at the specified position.
/// true if the end of string is found; false otherwise.
private static bool ReadLiteral(string text, int textIndex, string literal)
{
if (String.Compare(text, textIndex, literal, 0, literal.Length, StringComparison.Ordinal) != 0)
{
// Failed to find expected literal.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
return textIndex + literal.Length == text.Length;
}
///
/// Converts the specified character from the ASCII range to a digit.
///
/// Character to convert.
///
/// The Int32 value for c, or -1 if it is an element separator.
///
private static int DigitToInt32(char c)
{
if (c >= '0' && c <= '9')
{
return (int)(c - '0');
}
else
{
if (IsHttpElementSeparator(c))
{
return -1;
}
else
{
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
}
}
/// Returns all MIME types from the specified (non-blank) .
/// Non-blank text, as it appears on an HTTP Accepts header.
/// An enumerable object with media type descriptions.
private static IEnumerable MimeTypesFromAcceptHeader(string text)
{
Debug.Assert(!String.IsNullOrEmpty(text), "!String.IsNullOrEmpty(text)");
List mediaTypes = new List();
int textIndex = 0;
while (!SkipWhitespace(text, ref textIndex))
{
string type;
string subType;
ReadMediaTypeAndSubtype(text, ref textIndex, out type, out subType);
KeyValuePair[] parameters = null;
while (!SkipWhitespace(text, ref textIndex))
{
if (text[textIndex] == ',')
{
textIndex++;
break;
}
if (text[textIndex] != ';')
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter);
}
textIndex++;
if (SkipWhitespace(text, ref textIndex))
{
// ';' should be a leading separator, but we choose to be a
// bit permissive and allow it as a final delimiter as well.
break;
}
ReadMediaTypeParameter(text, ref textIndex, ref parameters);
}
mediaTypes.Add(new MediaType(type, subType, parameters));
}
return mediaTypes;
}
#endif
/// Read a parameter for a media type/range.
/// Text to read from.
/// Pointer in text.
/// Array with parameters to grow as necessary.
private static void ReadMediaTypeParameter(string text, ref int textIndex, ref KeyValuePair[] parameters)
{
int startIndex = textIndex;
if (ReadToken(text, ref textIndex))
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeMissingValue);
}
string parameterName = text.Substring(startIndex, textIndex - startIndex);
if (text[textIndex] != '=')
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeMissingValue);
}
textIndex++;
int valueStartIndex = textIndex;
ReadToken(text, ref textIndex);
string parameterValue = text.Substring(valueStartIndex, textIndex - valueStartIndex);
// Add the parameter name/value pair to the list.
if (parameters == null)
{
parameters = new KeyValuePair[1];
}
else
{
KeyValuePair[] grow = new KeyValuePair[parameters.Length + 1];
Array.Copy(parameters, grow, parameters.Length);
parameters = grow;
}
parameters[parameters.Length - 1] = new KeyValuePair(parameterName, parameterValue);
}
#if !ASTORIA_CLIENT
///
/// Reads the numeric part of a quality value substring, normalizing it to 0-1000
/// rather than the standard 0.000-1.000 ranges.
///
/// Text to read qvalue from.
/// Index into text where the qvalue starts.
/// After the method executes, the normalized qvalue.
///
/// For more information, see RFC 2616.3.8.
///
private static void ReadQualityValue(string text, ref int textIndex, out int qualityValue)
{
char digit = text[textIndex++];
if (digit == '0')
{
qualityValue = 0;
}
else if (digit == '1')
{
qualityValue = 1;
}
else
{
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
if (textIndex < text.Length && text[textIndex] == '.')
{
textIndex++;
int adjustFactor = 1000;
while (adjustFactor > 1 && textIndex < text.Length)
{
char c = text[textIndex];
int charValue = DigitToInt32(c);
if (charValue >= 0)
{
textIndex++;
adjustFactor /= 10;
qualityValue *= 10;
qualityValue += charValue;
}
else
{
break;
}
}
qualityValue = qualityValue *= adjustFactor;
if (qualityValue > 1000)
{
// Too high of a value in qvalue.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
}
else
{
qualityValue *= 1000;
}
}
///
/// Enumerates each charset part in the specified Accept-Charset header.
///
/// Non-null and non-empty header value for Accept-Charset.
///
/// A (non-sorted) enumeration of CharsetPart elements, which include
/// a charset name and a quality (preference) value, normalized to 0-1000.
///
private static IEnumerable AcceptCharsetParts(string headerValue)
{
Debug.Assert(!String.IsNullOrEmpty(headerValue), "!String.IsNullOrEmpty(headerValuer)");
// PERF: optimize for common patterns.
bool commaRequired = false; // Whether a comma should be found
int headerIndex = 0; // Index of character being procesed on headerValue.
int headerStart; // Index into headerValue for the start of the charset name.
int headerNameEnd; // Index into headerValue for the end of the charset name (+1).
int headerEnd; // Index into headerValue for this charset part (+1).
int qualityValue; // Normalized qvalue for this charset.
while (headerIndex < headerValue.Length)
{
if (SkipWhitespace(headerValue, ref headerIndex))
{
yield break;
}
if (headerValue[headerIndex] == ',')
{
commaRequired = false;
headerIndex++;
continue;
}
if (commaRequired)
{
// Comma missing between charset elements.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
headerStart = headerIndex;
headerNameEnd = headerStart;
bool endReached = ReadToken(headerValue, ref headerNameEnd);
if (headerNameEnd == headerIndex)
{
// Invalid charset name.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
if (endReached)
{
qualityValue = 1000;
headerEnd = headerNameEnd;
}
else
{
char afterNameChar = headerValue[headerNameEnd];
if (IsHttpSeparator(afterNameChar))
{
if (afterNameChar == ';')
{
if (ReadLiteral(headerValue, headerNameEnd, ";q="))
{
// Unexpected end of qvalue.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
headerEnd = headerNameEnd + 3;
ReadQualityValue(headerValue, ref headerEnd, out qualityValue);
}
else
{
qualityValue = 1000;
headerEnd = headerNameEnd;
}
}
else
{
// Invalid separator character.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
}
yield return new CharsetPart(headerValue.Substring(headerStart, headerNameEnd - headerStart), qualityValue);
// Prepare for next charset; we require at least one comma before we process it.
commaRequired = true;
headerIndex = headerEnd;
}
}
#endif
///
/// Determines whether the specified character is a valid HTTP separator.
///
/// Character to verify.
/// true if c is a separator; false otherwise.
///
/// See RFC 2616 2.2 for further information.
///
private static bool IsHttpSeparator(char c)
{
return
c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' ||
c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
c == '{' || c == '}' || c == ' ' || c == '\x9';
}
///
/// Determines whether the specified character is a valid HTTP header token character.
///
/// Character to verify.
/// true if c is a valid HTTP header token character; false otherwise.
private static bool IsHttpToken(char c)
{
// A token character is any character (0-127) except control (0-31) or
// separators. 127 is DEL, a control character.
return c < '\x7F' && c > '\x1F' && !IsHttpSeparator(c);
}
#if !ASTORIA_CLIENT
/// Provides a struct to encapsulate a charset name and its relative desirability.
private struct CharsetPart
{
/// Name of the charset.
internal readonly string Charset;
/// Charset quality (desirability), normalized to 0-1000.
internal readonly int Quality;
///
/// Initializes a new CharsetPart with the specified values.
///
/// Name of charset.
/// Charset quality (desirability), normalized to 0-1000.
internal CharsetPart(string charset, int quality)
{
Debug.Assert(charset != null, "charset != null");
Debug.Assert(charset.Length > 0, "charset.Length > 0");
Debug.Assert(0 <= quality && quality <= 1000, "0 <= quality && quality <= 1000");
this.Charset = charset;
this.Quality = quality;
}
}
#endif
/// Use this class to represent a media type definition.
[DebuggerDisplay("MediaType [{type}/{subType}]")]
private sealed class MediaType
{
/// Parameters specified on the media type.
private readonly KeyValuePair[] parameters;
/// Sub-type specification (for example, 'plain').
private readonly string subType;
/// Type specification (for example, 'text').
private readonly string type;
///
/// Initializes a new read-only instance.
///
/// Type specification (for example, 'text').
/// Sub-type specification (for example, 'plain').
/// Parameters specified on the media type.
internal MediaType(string type, string subType, KeyValuePair[] parameters)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(subType != null, "subType != null");
this.type = type;
this.subType = subType;
this.parameters = parameters;
}
/// Returns the MIME type in standard type/subtype form, without parameters.
internal string MimeType
{
get { return this.type + "/" + this.subType; }
}
/// media type parameters
internal KeyValuePair[] Parameters
{
get { return this.parameters; }
}
#if !ASTORIA_CLIENT
/// Gets a number of non-* matching types, or -1 if not matching at all.
/// Candidate MIME type to match.
/// The number of non-* matching types, or -1 if not matching at all.
internal int GetMatchingParts(string candidate)
{
int result = -1;
if (this.type == "*")
{
result = 0;
}
else
{
string candidateType = candidate.Substring(0, candidate.IndexOf('/'));
if (this.type == candidateType)
{
if (this.subType == "*")
{
result = 1;
}
else
{
string candidateSubType = candidate.Substring(candidateType.Length + 1);
if (this.subType == candidateSubType)
{
result = 2;
}
}
}
}
return result;
}
/// Selects a quality value for the specified type.
/// The quality value, in range from 0 through 1000.
/// See http://tools.ietf.org/html/rfc2616#section-14.1 for further details.
internal int SelectQualityValue()
{
if (this.parameters != null)
{
foreach (KeyValuePair parameter in this.parameters)
{
if (String.Equals(parameter.Key, XmlConstants.HttpQValueParameter, StringComparison.OrdinalIgnoreCase))
{
string qvalueText = parameter.Value.Trim();
if (qvalueText.Length > 0)
{
int result;
int textIndex = 0;
ReadQualityValue(qvalueText, ref textIndex, out result);
return result;
}
}
}
}
return 1000;
}
#endif
///
/// Selects the encoding appropriate for this media type specification
/// (possibly null).
///
///
/// The encoding explicitly defined on the media type specification, or
/// the default encoding for well-known media types.
///
///
/// As per http://tools.ietf.org/html/rfc2616#section-3.7, the type,
/// subtype and parameter name attributes are case-insensitive.
///
internal Encoding SelectEncoding()
{
if (this.parameters != null)
{
foreach (KeyValuePair parameter in this.parameters)
{
if (String.Equals(parameter.Key, XmlConstants.HttpCharsetParameter, StringComparison.OrdinalIgnoreCase))
{
string encodingName = parameter.Value.Trim();
if (encodingName.Length > 0)
{
return EncodingFromName(parameter.Value);
}
}
}
}
// Select the default encoding for this media type.
if (String.Equals(this.type, XmlConstants.MimeTextType, StringComparison.OrdinalIgnoreCase))
{
// HTTP 3.7.1 Canonicalization and Text Defaults
// "text" subtypes default to ISO-8859-1
//
// Unless the subtype is XML, in which case we should default
// to us-ascii. Instead we return null, to let the encoding
// in the PI win (http://tools.ietf.org/html/rfc3023#section-3.1)
if (String.Equals(this.subType, XmlConstants.MimeXmlSubType, StringComparison.OrdinalIgnoreCase))
{
return null;
}
else
{
return MissingEncoding;
}
}
else if (String.Equals(this.type, XmlConstants.MimeApplicationType, StringComparison.OrdinalIgnoreCase) &&
String.Equals(this.subType, XmlConstants.MimeJsonSubType, StringComparison.OrdinalIgnoreCase))
{
// http://tools.ietf.org/html/rfc4627#section-3
// The default encoding is UTF-8.
return FallbackEncoding;
}
else
{
return null;
}
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides a utility class to help in processing HTTP requests.
//
//
// @owner [....]
//---------------------------------------------------------------------
#if ASTORIA_CLIENT
namespace System.Data.Services.Client
#else
namespace System.Data.Services
#endif
{
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
/// Provides helper methods for processing HTTP requests.
internal static class HttpProcessUtility
{
/// UTF-8 encoding, without the BOM preamble.
///
/// While a BOM preamble on UTF8 is generally benign, it seems that some MIME handlers under IE6 will not
/// process the payload correctly when included.
///
/// Because the data service should include the encoding as part of the Content-Type in the response,
/// there should be no ambiguity as to what encoding is being used.
///
/// For further information, see http://www.unicode.org/faq/utf_bom.html#BOM.
///
internal static readonly UTF8Encoding EncodingUtf8NoPreamble = new UTF8Encoding(false, true);
/// Encoding to fall back to an appropriate encoding is not available.
internal static Encoding FallbackEncoding
{
get
{
return EncodingUtf8NoPreamble;
}
}
/// Encoding implied by an unspecified encoding value.
/// See http://tools.ietf.org/html/rfc2616#section-3.4.1 for details.
private static Encoding MissingEncoding
{
get
{
#if ASTORIA_LIGHT // ISO-8859-1 not available
return Encoding.UTF8;
#else
return Encoding.GetEncoding("ISO-8859-1", new EncoderExceptionFallback(), new DecoderExceptionFallback());
#endif
}
}
#if !ASTORIA_CLIENT
/// Builds a Content-Type header which includes MIME type and encoding information.
/// MIME type to be used.
/// Encoding to be used in response, possibly null.
/// The value for the Content-Type header.
internal static string BuildContentType(string mime, Encoding encoding)
{
Debug.Assert(mime != null, "mime != null");
if (encoding == null)
{
return mime;
}
else
{
return mime + ";charset=" + encoding.WebName;
}
}
/// Selects an acceptable MIME type that satisfies the Accepts header.
/// Text for Accepts header.
///
/// Types that the server is willing to return, in descending order
/// of preference.
///
/// The best MIME type for the client
internal static string SelectMimeType(string acceptTypesText, string[] availableTypes)
{
Debug.Assert(availableTypes != null, "acceptableTypes != null");
string selectedContentType = null;
int selectedMatchingParts = -1;
int selectedQualityValue = 0;
int selectedPreferenceIndex = Int32.MaxValue;
bool acceptable = false;
bool acceptTypesEmpty = true;
if (!String.IsNullOrEmpty(acceptTypesText))
{
IEnumerable acceptTypes = MimeTypesFromAcceptHeader(acceptTypesText);
foreach (MediaType acceptType in acceptTypes)
{
acceptTypesEmpty = false;
for (int i = 0; i < availableTypes.Length; i++)
{
string availableType = availableTypes[i];
int matchingParts = acceptType.GetMatchingParts(availableType);
if (matchingParts < 0)
{
continue;
}
if (matchingParts > selectedMatchingParts)
{
// A more specific type wins.
selectedContentType = availableType;
selectedMatchingParts = matchingParts;
selectedQualityValue = acceptType.SelectQualityValue();
selectedPreferenceIndex = i;
acceptable = selectedQualityValue != 0;
}
else if (matchingParts == selectedMatchingParts)
{
// A type with a higher q-value wins.
int candidateQualityValue = acceptType.SelectQualityValue();
if (candidateQualityValue > selectedQualityValue)
{
selectedContentType = availableType;
selectedQualityValue = candidateQualityValue;
selectedPreferenceIndex = i;
acceptable = selectedQualityValue != 0;
}
else if (candidateQualityValue == selectedQualityValue)
{
// A type that is earlier in the availableTypes array wins.
if (i < selectedPreferenceIndex)
{
selectedContentType = availableType;
selectedPreferenceIndex = i;
}
}
}
}
}
}
if (acceptTypesEmpty)
{
selectedContentType = availableTypes[0];
}
else if (!acceptable)
{
selectedContentType = null;
}
return selectedContentType;
}
/// Gets the appropriate MIME type for the request, throwing if there is none.
/// Text as it appears in an HTTP Accepts header.
/// Preferred content type to match if an exact media type is given - this is in descending order of preference.
/// Preferred fallback content type for inexact matches.
/// One of exactContentType or inexactContentType.
internal static string SelectRequiredMimeType(
string acceptTypesText,
string[] exactContentType,
string inexactContentType)
{
Debug.Assert(exactContentType != null && exactContentType.Length != 0, "exactContentType != null && exactContentType.Length != 0");
Debug.Assert(inexactContentType != null, "inexactContentType != null");
string selectedContentType = null;
int selectedMatchingParts = -1;
int selectedQualityValue = 0;
bool acceptable = false;
bool acceptTypesEmpty = true;
bool foundExactMatch = false;
if (!String.IsNullOrEmpty(acceptTypesText))
{
IEnumerable acceptTypes = MimeTypesFromAcceptHeader(acceptTypesText);
foreach (MediaType acceptType in acceptTypes)
{
acceptTypesEmpty = false;
for (int i = 0; i < exactContentType.Length; i++)
{
if (acceptType.MimeType == exactContentType[i])
{
selectedContentType = exactContentType[i];
selectedQualityValue = acceptType.SelectQualityValue();
acceptable = selectedQualityValue != 0;
foundExactMatch = true;
break;
}
}
if (foundExactMatch)
{
break;
}
int matchingParts = acceptType.GetMatchingParts(inexactContentType);
if (matchingParts < 0)
{
continue;
}
if (matchingParts > selectedMatchingParts)
{
// A more specific type wins.
selectedContentType = inexactContentType;
selectedMatchingParts = matchingParts;
selectedQualityValue = acceptType.SelectQualityValue();
acceptable = selectedQualityValue != 0;
}
else if (matchingParts == selectedMatchingParts)
{
// A type with a higher q-value wins.
int candidateQualityValue = acceptType.SelectQualityValue();
if (candidateQualityValue > selectedQualityValue)
{
selectedContentType = inexactContentType;
selectedQualityValue = candidateQualityValue;
acceptable = selectedQualityValue != 0;
}
}
}
}
if (!acceptable && !acceptTypesEmpty)
{
throw Error.HttpHeaderFailure(415, Strings.DataServiceException_UnsupportedMediaType);
}
if (acceptTypesEmpty)
{
Debug.Assert(selectedContentType == null, "selectedContentType == null - otherwise accept types were not empty");
selectedContentType = inexactContentType;
}
Debug.Assert(selectedContentType != null, "selectedContentType != null - otherwise no selection was made");
return selectedContentType;
}
/// Gets the best encoding available for the specified charset request.
///
/// The Accept-Charset header value (eg: "iso-8859-5, unicode-1-1;q=0.8").
///
/// An Encoding object appropriate to the specifed charset request.
internal static Encoding EncodingFromAcceptCharset(string acceptCharset)
{
// Determines the appropriate encoding mapping according to
// RFC 2616.14.2 (http://tools.ietf.org/html/rfc2616#section-14.2).
Encoding result = null;
if (!string.IsNullOrEmpty(acceptCharset))
{
// PERF: keep a cache of original strings to resolved Encoding.
List parts = new List(AcceptCharsetParts(acceptCharset));
parts.Sort(delegate(CharsetPart x, CharsetPart y)
{
return y.Quality - x.Quality;
});
var encoderFallback = new EncoderExceptionFallback();
var decoderFallback = new DecoderExceptionFallback();
foreach (CharsetPart part in parts)
{
if (part.Quality > 0)
{
// When UTF-8 is specified, select the version that doesn't use the BOM.
if (String.Compare("utf-8", part.Charset, StringComparison.OrdinalIgnoreCase) == 0)
{
result = FallbackEncoding;
break;
}
else
{
try
{
result = Encoding.GetEncoding(part.Charset, encoderFallback, decoderFallback);
break;
}
catch (ArgumentException)
{
// This exception is thrown when the character
// set isn't supported - it is ignored so
// other possible charsets are evaluated.
}
}
}
}
}
// No Charset was specifed, or if charsets were specified, no valid charset was found.
// Returning a different charset is also valid.
if (result == null)
{
result = FallbackEncoding;
}
return result;
}
#endif
/// Reads a Content-Type header and extracts the MIME type/subtype and encoding.
/// The Content-Type header.
/// The MIME type in standard type/subtype form, without parameters.
/// Encoding (possibly null).
/// parameters of content type
internal static KeyValuePair[] ReadContentType(string contentType, out string mime, out Encoding encoding)
{
if (String.IsNullOrEmpty(contentType))
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_ContentTypeMissing);
}
MediaType mediaType = ReadMediaType(contentType);
mime = mediaType.MimeType;
encoding = mediaType.SelectEncoding();
return mediaType.Parameters;
}
#if !ASTORIA_CLIENT
///
/// Given the parameters, search for the parameter with the given name and returns its value.
///
/// list of parameters specified.
/// name of the parameter whose value needs to be returned.
/// returns the value of the parameter with the given name. Returns null, if the parameter is not found.
internal static string GetParameterValue(KeyValuePair[] parameters, string parameterName)
{
if (parameters == null)
{
return null;
}
foreach (KeyValuePair parameterInfo in parameters)
{
if (parameterInfo.Key == parameterName)
{
return parameterInfo.Value;
}
}
return null;
}
#endif
/// Tries to read an ADO.NET Data Service version string.
/// Text to read.
/// Parsed version and trailing text.
/// true if the version was read successfully; false otherwise.
internal static bool TryReadVersion(string text, out KeyValuePair result)
{
Debug.Assert(text != null, "text != null");
// Separate version number and extra string.
int separator = text.IndexOf(';');
string versionText, libraryName;
if (separator >= 0)
{
versionText = text.Substring(0, separator);
libraryName = text.Substring(separator + 1).Trim();
}
else
{
versionText = text;
libraryName = null;
}
result = default(KeyValuePair);
versionText = versionText.Trim();
// The Version constructor allows for a more complex syntax, including
// build, revisions, and major/minor for revisions. We only take two
// number parts separated by a single dot.
bool dotFound = false;
for (int i = 0; i < versionText.Length; i++)
{
if (versionText[i] == '.')
{
if (dotFound)
{
return false;
}
dotFound = true;
}
else if (versionText[i] < '0' || versionText[i] > '9')
{
return false;
}
}
try
{
result = new KeyValuePair(new Version(versionText), libraryName);
return true;
}
catch (Exception e)
{
if (e is FormatException || e is OverflowException || e is ArgumentException)
{
return false;
}
throw;
}
}
/// Gets the named encoding if specified.
/// Name (possibly null or empty).
///
/// The named encoding if specified; the encoding for HTTP missing
/// charset specification otherwise.
///
///
/// See http://tools.ietf.org/html/rfc2616#section-3.4.1 for details.
///
private static Encoding EncodingFromName(string name)
{
if (name == null)
{
return MissingEncoding;
}
name = name.Trim();
if (name.Length == 0)
{
return MissingEncoding;
}
else
{
try
{
return Encoding.GetEncoding(name);
}
catch (ArgumentException)
{
// 400 - Bad Request
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EncodingNotSupported(name));
}
}
}
#if !ASTORIA_CLIENT
/// Creates a new exception for parsing errors.
/// Message for error.
/// A new exception that can be thrown for a parsing error.
private static DataServiceException CreateParsingException(string message)
{
// Status code "400" ; Section 10.4.1: Bad Request
return Error.HttpHeaderFailure(400, message);
}
#endif
/// Reads the type and subtype specifications for a MIME type.
/// Text in which specification exists.
/// Pointer into text.
/// Type of media found.
/// Subtype of media found.
private static void ReadMediaTypeAndSubtype(string text, ref int textIndex, out string type, out string subType)
{
Debug.Assert(text != null, "text != null");
int textStart = textIndex;
if (ReadToken(text, ref textIndex))
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeUnspecified);
}
if (text[textIndex] != '/')
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSlash);
}
type = text.Substring(textStart, textIndex - textStart);
textIndex++;
int subTypeStart = textIndex;
ReadToken(text, ref textIndex);
if (textIndex == subTypeStart)
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSubType);
}
subType = text.Substring(subTypeStart, textIndex - subTypeStart);
}
/// Reads a media type definition as used in a Content-Type header.
/// Text to read.
/// The defined by the specified
/// All syntactic errors will produce a 400 - Bad Request status code.
private static MediaType ReadMediaType(string text)
{
Debug.Assert(text != null, "text != null");
string type;
string subType;
int textIndex = 0;
ReadMediaTypeAndSubtype(text, ref textIndex, out type, out subType);
KeyValuePair[] parameters = null;
while (!SkipWhitespace(text, ref textIndex))
{
if (text[textIndex] != ';')
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter);
}
textIndex++;
if (SkipWhitespace(text, ref textIndex))
{
// ';' should be a leading separator, but we choose to be a
// bit permissive and allow it as a final delimiter as well.
break;
}
ReadMediaTypeParameter(text, ref textIndex, ref parameters);
}
return new MediaType(type, subType, parameters);
}
///
/// Reads a token on the specified text by advancing an index on it.
///
/// Text to read token from.
/// Index for the position being scanned on text.
/// true if the end of the text was reached; false otherwise.
private static bool ReadToken(string text, ref int textIndex)
{
while (textIndex < text.Length && IsHttpToken(text[textIndex]))
{
textIndex++;
}
return (textIndex == text.Length);
}
///
/// Skips whitespace in the specified text by advancing an index to
/// the next non-whitespace character.
///
/// Text to scan.
/// Index to begin scanning from.
/// true if the end of the string was reached, false otherwise.
private static bool SkipWhitespace(string text, ref int textIndex)
{
Debug.Assert(text != null, "text != null");
Debug.Assert(text.Length >= 0, "text >= 0");
Debug.Assert(textIndex <= text.Length, "text <= text.Length");
while (textIndex < text.Length && Char.IsWhiteSpace(text, textIndex))
{
textIndex++;
}
return (textIndex == text.Length);
}
#if !ASTORIA_CLIENT
///
/// Verfies whether the specified character is a valid separator in
/// an HTTP header list of element.
///
/// Character to verify.
/// true if c is a valid character for separating elements; false otherwise.
private static bool IsHttpElementSeparator(char c)
{
return c == ',' || c == ' ' || c == '\t';
}
///
/// "Reads" a literal from the specified string by verifying that
/// the exact text can be found at the specified position.
///
/// Text within which a literal should be checked.
/// Index in text where the literal should be found.
/// Literal to check at the specified position.
/// true if the end of string is found; false otherwise.
private static bool ReadLiteral(string text, int textIndex, string literal)
{
if (String.Compare(text, textIndex, literal, 0, literal.Length, StringComparison.Ordinal) != 0)
{
// Failed to find expected literal.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
return textIndex + literal.Length == text.Length;
}
///
/// Converts the specified character from the ASCII range to a digit.
///
/// Character to convert.
///
/// The Int32 value for c, or -1 if it is an element separator.
///
private static int DigitToInt32(char c)
{
if (c >= '0' && c <= '9')
{
return (int)(c - '0');
}
else
{
if (IsHttpElementSeparator(c))
{
return -1;
}
else
{
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
}
}
/// Returns all MIME types from the specified (non-blank) .
/// Non-blank text, as it appears on an HTTP Accepts header.
/// An enumerable object with media type descriptions.
private static IEnumerable MimeTypesFromAcceptHeader(string text)
{
Debug.Assert(!String.IsNullOrEmpty(text), "!String.IsNullOrEmpty(text)");
List mediaTypes = new List();
int textIndex = 0;
while (!SkipWhitespace(text, ref textIndex))
{
string type;
string subType;
ReadMediaTypeAndSubtype(text, ref textIndex, out type, out subType);
KeyValuePair[] parameters = null;
while (!SkipWhitespace(text, ref textIndex))
{
if (text[textIndex] == ',')
{
textIndex++;
break;
}
if (text[textIndex] != ';')
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter);
}
textIndex++;
if (SkipWhitespace(text, ref textIndex))
{
// ';' should be a leading separator, but we choose to be a
// bit permissive and allow it as a final delimiter as well.
break;
}
ReadMediaTypeParameter(text, ref textIndex, ref parameters);
}
mediaTypes.Add(new MediaType(type, subType, parameters));
}
return mediaTypes;
}
#endif
/// Read a parameter for a media type/range.
/// Text to read from.
/// Pointer in text.
/// Array with parameters to grow as necessary.
private static void ReadMediaTypeParameter(string text, ref int textIndex, ref KeyValuePair[] parameters)
{
int startIndex = textIndex;
if (ReadToken(text, ref textIndex))
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeMissingValue);
}
string parameterName = text.Substring(startIndex, textIndex - startIndex);
if (text[textIndex] != '=')
{
throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeMissingValue);
}
textIndex++;
int valueStartIndex = textIndex;
ReadToken(text, ref textIndex);
string parameterValue = text.Substring(valueStartIndex, textIndex - valueStartIndex);
// Add the parameter name/value pair to the list.
if (parameters == null)
{
parameters = new KeyValuePair[1];
}
else
{
KeyValuePair[] grow = new KeyValuePair[parameters.Length + 1];
Array.Copy(parameters, grow, parameters.Length);
parameters = grow;
}
parameters[parameters.Length - 1] = new KeyValuePair(parameterName, parameterValue);
}
#if !ASTORIA_CLIENT
///
/// Reads the numeric part of a quality value substring, normalizing it to 0-1000
/// rather than the standard 0.000-1.000 ranges.
///
/// Text to read qvalue from.
/// Index into text where the qvalue starts.
/// After the method executes, the normalized qvalue.
///
/// For more information, see RFC 2616.3.8.
///
private static void ReadQualityValue(string text, ref int textIndex, out int qualityValue)
{
char digit = text[textIndex++];
if (digit == '0')
{
qualityValue = 0;
}
else if (digit == '1')
{
qualityValue = 1;
}
else
{
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
if (textIndex < text.Length && text[textIndex] == '.')
{
textIndex++;
int adjustFactor = 1000;
while (adjustFactor > 1 && textIndex < text.Length)
{
char c = text[textIndex];
int charValue = DigitToInt32(c);
if (charValue >= 0)
{
textIndex++;
adjustFactor /= 10;
qualityValue *= 10;
qualityValue += charValue;
}
else
{
break;
}
}
qualityValue = qualityValue *= adjustFactor;
if (qualityValue > 1000)
{
// Too high of a value in qvalue.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
}
else
{
qualityValue *= 1000;
}
}
///
/// Enumerates each charset part in the specified Accept-Charset header.
///
/// Non-null and non-empty header value for Accept-Charset.
///
/// A (non-sorted) enumeration of CharsetPart elements, which include
/// a charset name and a quality (preference) value, normalized to 0-1000.
///
private static IEnumerable AcceptCharsetParts(string headerValue)
{
Debug.Assert(!String.IsNullOrEmpty(headerValue), "!String.IsNullOrEmpty(headerValuer)");
// PERF: optimize for common patterns.
bool commaRequired = false; // Whether a comma should be found
int headerIndex = 0; // Index of character being procesed on headerValue.
int headerStart; // Index into headerValue for the start of the charset name.
int headerNameEnd; // Index into headerValue for the end of the charset name (+1).
int headerEnd; // Index into headerValue for this charset part (+1).
int qualityValue; // Normalized qvalue for this charset.
while (headerIndex < headerValue.Length)
{
if (SkipWhitespace(headerValue, ref headerIndex))
{
yield break;
}
if (headerValue[headerIndex] == ',')
{
commaRequired = false;
headerIndex++;
continue;
}
if (commaRequired)
{
// Comma missing between charset elements.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
headerStart = headerIndex;
headerNameEnd = headerStart;
bool endReached = ReadToken(headerValue, ref headerNameEnd);
if (headerNameEnd == headerIndex)
{
// Invalid charset name.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
if (endReached)
{
qualityValue = 1000;
headerEnd = headerNameEnd;
}
else
{
char afterNameChar = headerValue[headerNameEnd];
if (IsHttpSeparator(afterNameChar))
{
if (afterNameChar == ';')
{
if (ReadLiteral(headerValue, headerNameEnd, ";q="))
{
// Unexpected end of qvalue.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
headerEnd = headerNameEnd + 3;
ReadQualityValue(headerValue, ref headerEnd, out qualityValue);
}
else
{
qualityValue = 1000;
headerEnd = headerNameEnd;
}
}
else
{
// Invalid separator character.
throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue);
}
}
yield return new CharsetPart(headerValue.Substring(headerStart, headerNameEnd - headerStart), qualityValue);
// Prepare for next charset; we require at least one comma before we process it.
commaRequired = true;
headerIndex = headerEnd;
}
}
#endif
///
/// Determines whether the specified character is a valid HTTP separator.
///
/// Character to verify.
/// true if c is a separator; false otherwise.
///
/// See RFC 2616 2.2 for further information.
///
private static bool IsHttpSeparator(char c)
{
return
c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' ||
c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
c == '{' || c == '}' || c == ' ' || c == '\x9';
}
///
/// Determines whether the specified character is a valid HTTP header token character.
///
/// Character to verify.
/// true if c is a valid HTTP header token character; false otherwise.
private static bool IsHttpToken(char c)
{
// A token character is any character (0-127) except control (0-31) or
// separators. 127 is DEL, a control character.
return c < '\x7F' && c > '\x1F' && !IsHttpSeparator(c);
}
#if !ASTORIA_CLIENT
/// Provides a struct to encapsulate a charset name and its relative desirability.
private struct CharsetPart
{
/// Name of the charset.
internal readonly string Charset;
/// Charset quality (desirability), normalized to 0-1000.
internal readonly int Quality;
///
/// Initializes a new CharsetPart with the specified values.
///
/// Name of charset.
/// Charset quality (desirability), normalized to 0-1000.
internal CharsetPart(string charset, int quality)
{
Debug.Assert(charset != null, "charset != null");
Debug.Assert(charset.Length > 0, "charset.Length > 0");
Debug.Assert(0 <= quality && quality <= 1000, "0 <= quality && quality <= 1000");
this.Charset = charset;
this.Quality = quality;
}
}
#endif
/// Use this class to represent a media type definition.
[DebuggerDisplay("MediaType [{type}/{subType}]")]
private sealed class MediaType
{
/// Parameters specified on the media type.
private readonly KeyValuePair[] parameters;
/// Sub-type specification (for example, 'plain').
private readonly string subType;
/// Type specification (for example, 'text').
private readonly string type;
///
/// Initializes a new read-only instance.
///
/// Type specification (for example, 'text').
/// Sub-type specification (for example, 'plain').
/// Parameters specified on the media type.
internal MediaType(string type, string subType, KeyValuePair[] parameters)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(subType != null, "subType != null");
this.type = type;
this.subType = subType;
this.parameters = parameters;
}
/// Returns the MIME type in standard type/subtype form, without parameters.
internal string MimeType
{
get { return this.type + "/" + this.subType; }
}
/// media type parameters
internal KeyValuePair[] Parameters
{
get { return this.parameters; }
}
#if !ASTORIA_CLIENT
/// Gets a number of non-* matching types, or -1 if not matching at all.
/// Candidate MIME type to match.
/// The number of non-* matching types, or -1 if not matching at all.
internal int GetMatchingParts(string candidate)
{
int result = -1;
if (this.type == "*")
{
result = 0;
}
else
{
string candidateType = candidate.Substring(0, candidate.IndexOf('/'));
if (this.type == candidateType)
{
if (this.subType == "*")
{
result = 1;
}
else
{
string candidateSubType = candidate.Substring(candidateType.Length + 1);
if (this.subType == candidateSubType)
{
result = 2;
}
}
}
}
return result;
}
/// Selects a quality value for the specified type.
/// The quality value, in range from 0 through 1000.
/// See http://tools.ietf.org/html/rfc2616#section-14.1 for further details.
internal int SelectQualityValue()
{
if (this.parameters != null)
{
foreach (KeyValuePair parameter in this.parameters)
{
if (String.Equals(parameter.Key, XmlConstants.HttpQValueParameter, StringComparison.OrdinalIgnoreCase))
{
string qvalueText = parameter.Value.Trim();
if (qvalueText.Length > 0)
{
int result;
int textIndex = 0;
ReadQualityValue(qvalueText, ref textIndex, out result);
return result;
}
}
}
}
return 1000;
}
#endif
///
/// Selects the encoding appropriate for this media type specification
/// (possibly null).
///
///
/// The encoding explicitly defined on the media type specification, or
/// the default encoding for well-known media types.
///
///
/// As per http://tools.ietf.org/html/rfc2616#section-3.7, the type,
/// subtype and parameter name attributes are case-insensitive.
///
internal Encoding SelectEncoding()
{
if (this.parameters != null)
{
foreach (KeyValuePair parameter in this.parameters)
{
if (String.Equals(parameter.Key, XmlConstants.HttpCharsetParameter, StringComparison.OrdinalIgnoreCase))
{
string encodingName = parameter.Value.Trim();
if (encodingName.Length > 0)
{
return EncodingFromName(parameter.Value);
}
}
}
}
// Select the default encoding for this media type.
if (String.Equals(this.type, XmlConstants.MimeTextType, StringComparison.OrdinalIgnoreCase))
{
// HTTP 3.7.1 Canonicalization and Text Defaults
// "text" subtypes default to ISO-8859-1
//
// Unless the subtype is XML, in which case we should default
// to us-ascii. Instead we return null, to let the encoding
// in the PI win (http://tools.ietf.org/html/rfc3023#section-3.1)
if (String.Equals(this.subType, XmlConstants.MimeXmlSubType, StringComparison.OrdinalIgnoreCase))
{
return null;
}
else
{
return MissingEncoding;
}
}
else if (String.Equals(this.type, XmlConstants.MimeApplicationType, StringComparison.OrdinalIgnoreCase) &&
String.Equals(this.subType, XmlConstants.MimeJsonSubType, StringComparison.OrdinalIgnoreCase))
{
// http://tools.ietf.org/html/rfc4627#section-3
// The default encoding is UTF-8.
return FallbackEncoding;
}
else
{
return null;
}
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- TypeCacheManager.cs
- TripleDESCryptoServiceProvider.cs
- DataListItem.cs
- BigIntegerStorage.cs
- SessionState.cs
- SiteOfOriginPart.cs
- DBConcurrencyException.cs
- ObjectListItem.cs
- PostBackTrigger.cs
- HistoryEventArgs.cs
- HealthMonitoringSectionHelper.cs
- _AutoWebProxyScriptHelper.cs
- RowTypeElement.cs
- CapabilitiesState.cs
- CryptoProvider.cs
- CodeCompileUnit.cs
- MenuBase.cs
- PathNode.cs
- EntityCommandDefinition.cs
- TextBounds.cs
- MissingMethodException.cs
- ToolStripItemGlyph.cs
- XmlTextWriter.cs
- DocumentPage.cs
- CustomWebEventKey.cs
- CollectionConverter.cs
- ValueChangedEventManager.cs
- WebPartConnectionsEventArgs.cs
- MobileComponentEditorPage.cs
- CodeNamespaceImport.cs
- LongValidator.cs
- ItemsChangedEventArgs.cs
- GridLength.cs
- EmptyStringExpandableObjectConverter.cs
- MatchSingleFxEngineOpcode.cs
- Base64Encoder.cs
- RadioButtonFlatAdapter.cs
- StringOutput.cs
- Pair.cs
- SByteStorage.cs
- SerialStream.cs
- UIElementParagraph.cs
- PhysicalAddress.cs
- HotCommands.cs
- SiteMapNode.cs
- SoapException.cs
- ExpressionWriter.cs
- GenericAuthenticationEventArgs.cs
- ChangeInterceptorAttribute.cs
- QueryAsyncResult.cs
- PriorityBindingExpression.cs
- IntegrationExceptionEventArgs.cs
- Debug.cs
- XmlILIndex.cs
- UdpMessageProperty.cs
- DataGridViewAutoSizeModeEventArgs.cs
- _NestedSingleAsyncResult.cs
- DocumentGrid.cs
- ValueCollectionParameterReader.cs
- DataListItemEventArgs.cs
- BrushValueSerializer.cs
- SqlMethodAttribute.cs
- DisplayMemberTemplateSelector.cs
- RequestQueue.cs
- AsyncResult.cs
- WebPartDisplayMode.cs
- NetworkCredential.cs
- CodeMemberProperty.cs
- TreeViewCancelEvent.cs
- ItemContainerGenerator.cs
- SchemaLookupTable.cs
- MenuEventArgs.cs
- HashMembershipCondition.cs
- StylusButton.cs
- WebException.cs
- NullableConverter.cs
- CqlErrorHelper.cs
- BevelBitmapEffect.cs
- RightsManagementResourceHelper.cs
- ScopedMessagePartSpecification.cs
- TableLayoutPanelDesigner.cs
- ParameterCollection.cs
- GetPageNumberCompletedEventArgs.cs
- ContextMenuStrip.cs
- HttpProfileBase.cs
- PropertyIDSet.cs
- RuleSettingsCollection.cs
- Operand.cs
- SocketElement.cs
- Timer.cs
- XmlQueryRuntime.cs
- ISAPIWorkerRequest.cs
- TextPattern.cs
- Emitter.cs
- DateTimeUtil.cs
- TableRowCollection.cs
- QilIterator.cs
- Point.cs
- HijriCalendar.cs
- XPathDocument.cs