//Copyright 2010 Microsoft Corporation // //Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. //You may obtain a copy of the License at // //http://www.apache.org/licenses/LICENSE-2.0 // //Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an //"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //See the License for the specific language governing permissions and limitations under the License. namespace System.Data.Services.Client { using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; internal static class HttpProcessUtility { internal static readonly UTF8Encoding EncodingUtf8NoPreamble = new UTF8Encoding(false, true); internal static Encoding FallbackEncoding { get { return EncodingUtf8NoPreamble; } } private static Encoding MissingEncoding { get { #if ASTORIA_LIGHT return Encoding.UTF8; #else return Encoding.GetEncoding("ISO-8859-1", new EncoderExceptionFallback(), new DecoderExceptionFallback()); #endif } } 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; } internal static bool TryReadVersion(string text, out KeyValuePair result) { Debug.Assert(text != null, "text != null"); 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(); 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; } } private static Encoding EncodingFromName(string name) { if (name == null) { return MissingEncoding; } name = name.Trim(); if (name.Length == 0) { return MissingEncoding; } else { try { #if ASTORIA_LIGHT return Encoding.UTF8; #else return Encoding.GetEncoding(name); #endif } catch (ArgumentException) { throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EncodingNotSupported(name)); } } } 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); } 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)) { break; } ReadMediaTypeParameter(text, ref textIndex, ref parameters); } return new MediaType(type, subType, parameters); } private static bool ReadToken(string text, ref int textIndex) { while (textIndex < text.Length && IsHttpToken(text[textIndex])) { textIndex++; } return (textIndex == text.Length); } 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); } 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++; string parameterValue = ReadQuotedParameterValue(parameterName, text, ref textIndex); 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); } private static string ReadQuotedParameterValue(string parameterName, string headerText, ref int textIndex) { StringBuilder parameterValue = new StringBuilder(); bool valueIsQuoted = false; if (textIndex < headerText.Length) { if (headerText[textIndex] == '\"') { textIndex++; valueIsQuoted = true; } } while (textIndex < headerText.Length) { char currentChar = headerText[textIndex]; if (currentChar == '\\' || currentChar == '\"') { if (!valueIsQuoted) { throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EscapeCharWithoutQuotes(parameterName)); } textIndex++; if (currentChar == '\"') { valueIsQuoted = false; break; } if (textIndex >= headerText.Length) { throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EscapeCharAtEnd(parameterName)); } currentChar = headerText[textIndex]; } else if (!IsHttpToken(currentChar)) { break; } parameterValue.Append(currentChar); textIndex++; } if (valueIsQuoted) { throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_ClosingQuoteNotFound(parameterName)); } return parameterValue.ToString(); } 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'; } private static bool IsHttpToken(char c) { return c < '\x7F' && c > '\x1F' && !IsHttpSeparator(c); } [DebuggerDisplay("MediaType [{type}/{subType}]")] private sealed class MediaType { private readonly KeyValuePair[] parameters; private readonly string subType; private readonly string 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; } internal string MimeType { get { return this.type + "/" + this.subType; } } internal KeyValuePair[] Parameters { get { return this.parameters; } } 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); } } } } if (String.Equals(this.type, XmlConstants.MimeTextType, StringComparison.OrdinalIgnoreCase)) { 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)) { return FallbackEncoding; } else { return null; } } } } }