HttpProcessUtility.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. //Copyright 2010 Microsoft Corporation
  2. //
  3. //Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
  4. //You may obtain a copy of the License at
  5. //
  6. //http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. //Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
  9. //"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. //See the License for the specific language governing permissions and limitations under the License.
  11. namespace System.Data.Services.Client
  12. {
  13. using System;
  14. using System.Collections.Generic;
  15. using System.Diagnostics;
  16. using System.Text;
  17. internal static class HttpProcessUtility
  18. {
  19. internal static readonly UTF8Encoding EncodingUtf8NoPreamble = new UTF8Encoding(false, true);
  20. internal static Encoding FallbackEncoding
  21. {
  22. get
  23. {
  24. return EncodingUtf8NoPreamble;
  25. }
  26. }
  27. private static Encoding MissingEncoding
  28. {
  29. get
  30. {
  31. #if ASTORIA_LIGHT
  32. return Encoding.UTF8;
  33. #else
  34. return Encoding.GetEncoding("ISO-8859-1", new EncoderExceptionFallback(), new DecoderExceptionFallback());
  35. #endif
  36. }
  37. }
  38. internal static KeyValuePair<string, string>[] ReadContentType(string contentType, out string mime, out Encoding encoding)
  39. {
  40. if (String.IsNullOrEmpty(contentType))
  41. {
  42. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_ContentTypeMissing);
  43. }
  44. MediaType mediaType = ReadMediaType(contentType);
  45. mime = mediaType.MimeType;
  46. encoding = mediaType.SelectEncoding();
  47. return mediaType.Parameters;
  48. }
  49. internal static bool TryReadVersion(string text, out KeyValuePair<Version, string> result)
  50. {
  51. Debug.Assert(text != null, "text != null");
  52. int separator = text.IndexOf(';');
  53. string versionText, libraryName;
  54. if (separator >= 0)
  55. {
  56. versionText = text.Substring(0, separator);
  57. libraryName = text.Substring(separator + 1).Trim();
  58. }
  59. else
  60. {
  61. versionText = text;
  62. libraryName = null;
  63. }
  64. result = default(KeyValuePair<Version, string>);
  65. versionText = versionText.Trim();
  66. bool dotFound = false;
  67. for (int i = 0; i < versionText.Length; i++)
  68. {
  69. if (versionText[i] == '.')
  70. {
  71. if (dotFound)
  72. {
  73. return false;
  74. }
  75. dotFound = true;
  76. }
  77. else if (versionText[i] < '0' || versionText[i] > '9')
  78. {
  79. return false;
  80. }
  81. }
  82. try
  83. {
  84. result = new KeyValuePair<Version, string>(new Version(versionText), libraryName);
  85. return true;
  86. }
  87. catch (Exception e)
  88. {
  89. if (e is FormatException || e is OverflowException || e is ArgumentException)
  90. {
  91. return false;
  92. }
  93. throw;
  94. }
  95. }
  96. private static Encoding EncodingFromName(string name)
  97. {
  98. if (name == null)
  99. {
  100. return MissingEncoding;
  101. }
  102. name = name.Trim();
  103. if (name.Length == 0)
  104. {
  105. return MissingEncoding;
  106. }
  107. else
  108. {
  109. try
  110. {
  111. #if ASTORIA_LIGHT
  112. return Encoding.UTF8;
  113. #else
  114. return Encoding.GetEncoding(name);
  115. #endif
  116. }
  117. catch (ArgumentException)
  118. {
  119. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EncodingNotSupported(name));
  120. }
  121. }
  122. }
  123. private static void ReadMediaTypeAndSubtype(string text, ref int textIndex, out string type, out string subType)
  124. {
  125. Debug.Assert(text != null, "text != null");
  126. int textStart = textIndex;
  127. if (ReadToken(text, ref textIndex))
  128. {
  129. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeUnspecified);
  130. }
  131. if (text[textIndex] != '/')
  132. {
  133. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSlash);
  134. }
  135. type = text.Substring(textStart, textIndex - textStart);
  136. textIndex++;
  137. int subTypeStart = textIndex;
  138. ReadToken(text, ref textIndex);
  139. if (textIndex == subTypeStart)
  140. {
  141. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSubType);
  142. }
  143. subType = text.Substring(subTypeStart, textIndex - subTypeStart);
  144. }
  145. private static MediaType ReadMediaType(string text)
  146. {
  147. Debug.Assert(text != null, "text != null");
  148. string type;
  149. string subType;
  150. int textIndex = 0;
  151. ReadMediaTypeAndSubtype(text, ref textIndex, out type, out subType);
  152. KeyValuePair<string, string>[] parameters = null;
  153. while (!SkipWhitespace(text, ref textIndex))
  154. {
  155. if (text[textIndex] != ';')
  156. {
  157. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter);
  158. }
  159. textIndex++;
  160. if (SkipWhitespace(text, ref textIndex))
  161. {
  162. break;
  163. }
  164. ReadMediaTypeParameter(text, ref textIndex, ref parameters);
  165. }
  166. return new MediaType(type, subType, parameters);
  167. }
  168. private static bool ReadToken(string text, ref int textIndex)
  169. {
  170. while (textIndex < text.Length && IsHttpToken(text[textIndex]))
  171. {
  172. textIndex++;
  173. }
  174. return (textIndex == text.Length);
  175. }
  176. private static bool SkipWhitespace(string text, ref int textIndex)
  177. {
  178. Debug.Assert(text != null, "text != null");
  179. Debug.Assert(text.Length >= 0, "text >= 0");
  180. Debug.Assert(textIndex <= text.Length, "text <= text.Length");
  181. while (textIndex < text.Length && Char.IsWhiteSpace(text, textIndex))
  182. {
  183. textIndex++;
  184. }
  185. return (textIndex == text.Length);
  186. }
  187. private static void ReadMediaTypeParameter(string text, ref int textIndex, ref KeyValuePair<string, string>[] parameters)
  188. {
  189. int startIndex = textIndex;
  190. if (ReadToken(text, ref textIndex))
  191. {
  192. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeMissingValue);
  193. }
  194. string parameterName = text.Substring(startIndex, textIndex - startIndex);
  195. if (text[textIndex] != '=')
  196. {
  197. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeMissingValue);
  198. }
  199. textIndex++;
  200. string parameterValue = ReadQuotedParameterValue(parameterName, text, ref textIndex);
  201. if (parameters == null)
  202. {
  203. parameters = new KeyValuePair<string, string>[1];
  204. }
  205. else
  206. {
  207. KeyValuePair<string, string>[] grow = new KeyValuePair<string, string>[parameters.Length + 1];
  208. Array.Copy(parameters, grow, parameters.Length);
  209. parameters = grow;
  210. }
  211. parameters[parameters.Length - 1] = new KeyValuePair<string, string>(parameterName, parameterValue);
  212. }
  213. private static string ReadQuotedParameterValue(string parameterName, string headerText, ref int textIndex)
  214. {
  215. StringBuilder parameterValue = new StringBuilder();
  216. bool valueIsQuoted = false;
  217. if (textIndex < headerText.Length)
  218. {
  219. if (headerText[textIndex] == '\"')
  220. {
  221. textIndex++;
  222. valueIsQuoted = true;
  223. }
  224. }
  225. while (textIndex < headerText.Length)
  226. {
  227. char currentChar = headerText[textIndex];
  228. if (currentChar == '\\' || currentChar == '\"')
  229. {
  230. if (!valueIsQuoted)
  231. {
  232. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EscapeCharWithoutQuotes(parameterName));
  233. }
  234. textIndex++;
  235. if (currentChar == '\"')
  236. {
  237. valueIsQuoted = false;
  238. break;
  239. }
  240. if (textIndex >= headerText.Length)
  241. {
  242. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EscapeCharAtEnd(parameterName));
  243. }
  244. currentChar = headerText[textIndex];
  245. }
  246. else
  247. if (!IsHttpToken(currentChar))
  248. {
  249. break;
  250. }
  251. parameterValue.Append(currentChar);
  252. textIndex++;
  253. }
  254. if (valueIsQuoted)
  255. {
  256. throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_ClosingQuoteNotFound(parameterName));
  257. }
  258. return parameterValue.ToString();
  259. }
  260. private static bool IsHttpSeparator(char c)
  261. {
  262. return
  263. c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
  264. c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' ||
  265. c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
  266. c == '{' || c == '}' || c == ' ' || c == '\x9';
  267. }
  268. private static bool IsHttpToken(char c)
  269. {
  270. return c < '\x7F' && c > '\x1F' && !IsHttpSeparator(c);
  271. }
  272. [DebuggerDisplay("MediaType [{type}/{subType}]")]
  273. private sealed class MediaType
  274. {
  275. private readonly KeyValuePair<string, string>[] parameters;
  276. private readonly string subType;
  277. private readonly string type;
  278. internal MediaType(string type, string subType, KeyValuePair<string, string>[] parameters)
  279. {
  280. Debug.Assert(type != null, "type != null");
  281. Debug.Assert(subType != null, "subType != null");
  282. this.type = type;
  283. this.subType = subType;
  284. this.parameters = parameters;
  285. }
  286. internal string MimeType
  287. {
  288. get { return this.type + "/" + this.subType; }
  289. }
  290. internal KeyValuePair<string, string>[] Parameters
  291. {
  292. get { return this.parameters; }
  293. }
  294. internal Encoding SelectEncoding()
  295. {
  296. if (this.parameters != null)
  297. {
  298. foreach (KeyValuePair<string, string> parameter in this.parameters)
  299. {
  300. if (String.Equals(parameter.Key, XmlConstants.HttpCharsetParameter, StringComparison.OrdinalIgnoreCase))
  301. {
  302. string encodingName = parameter.Value.Trim();
  303. if (encodingName.Length > 0)
  304. {
  305. return EncodingFromName(parameter.Value);
  306. }
  307. }
  308. }
  309. }
  310. if (String.Equals(this.type, XmlConstants.MimeTextType, StringComparison.OrdinalIgnoreCase))
  311. {
  312. if (String.Equals(this.subType, XmlConstants.MimeXmlSubType, StringComparison.OrdinalIgnoreCase))
  313. {
  314. return null;
  315. }
  316. else
  317. {
  318. return MissingEncoding;
  319. }
  320. }
  321. else if (String.Equals(this.type, XmlConstants.MimeApplicationType, StringComparison.OrdinalIgnoreCase) &&
  322. String.Equals(this.subType, XmlConstants.MimeJsonSubType, StringComparison.OrdinalIgnoreCase))
  323. {
  324. return FallbackEncoding;
  325. }
  326. else
  327. {
  328. return null;
  329. }
  330. }
  331. }
  332. }
  333. }