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 12 13 namespace System.Data.Services.Client 14 { 15 using System; 16 using System.Collections.Generic; 17 using System.Diagnostics; 18 using System.Text; 19 20 internal static class HttpProcessUtility 21 { 22 internal static readonly UTF8Encoding EncodingUtf8NoPreamble = new UTF8Encoding(false, true); 23 24 internal static Encoding FallbackEncoding 25 { 26 get 27 { 28 return EncodingUtf8NoPreamble; 29 } 30 } 31 32 private static Encoding MissingEncoding 33 { 34 get 35 { 36 #if ASTORIA_LIGHT 37 return Encoding.UTF8; 38 #else 39 return Encoding.GetEncoding("ISO-8859-1", new EncoderExceptionFallback(), new DecoderExceptionFallback()); 40 #endif 41 } 42 } 43 44 ReadContentType(string contentType, out string mime, out Encoding encoding)45 internal static KeyValuePair<string, string>[] ReadContentType(string contentType, out string mime, out Encoding encoding) 46 { 47 if (String.IsNullOrEmpty(contentType)) 48 { 49 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_ContentTypeMissing); 50 } 51 52 MediaType mediaType = ReadMediaType(contentType); 53 mime = mediaType.MimeType; 54 encoding = mediaType.SelectEncoding(); 55 return mediaType.Parameters; 56 } 57 TryReadVersion(string text, out KeyValuePair<Version, string> result)58 internal static bool TryReadVersion(string text, out KeyValuePair<Version, string> result) 59 { 60 Debug.Assert(text != null, "text != null"); 61 62 int separator = text.IndexOf(';'); 63 string versionText, libraryName; 64 if (separator >= 0) 65 { 66 versionText = text.Substring(0, separator); 67 libraryName = text.Substring(separator + 1).Trim(); 68 } 69 else 70 { 71 versionText = text; 72 libraryName = null; 73 } 74 75 result = default(KeyValuePair<Version, string>); 76 versionText = versionText.Trim(); 77 78 bool dotFound = false; 79 for (int i = 0; i < versionText.Length; i++) 80 { 81 if (versionText[i] == '.') 82 { 83 if (dotFound) 84 { 85 return false; 86 } 87 88 dotFound = true; 89 } 90 else if (versionText[i] < '0' || versionText[i] > '9') 91 { 92 return false; 93 } 94 } 95 96 try 97 { 98 result = new KeyValuePair<Version, string>(new Version(versionText), libraryName); 99 return true; 100 } 101 catch (Exception e) 102 { 103 if (e is FormatException || e is OverflowException || e is ArgumentException) 104 { 105 return false; 106 } 107 108 throw; 109 } 110 } 111 EncodingFromName(string name)112 private static Encoding EncodingFromName(string name) 113 { 114 if (name == null) 115 { 116 return MissingEncoding; 117 } 118 119 name = name.Trim(); 120 if (name.Length == 0) 121 { 122 return MissingEncoding; 123 } 124 else 125 { 126 try 127 { 128 #if ASTORIA_LIGHT 129 return Encoding.UTF8; 130 #else 131 return Encoding.GetEncoding(name); 132 #endif 133 } 134 catch (ArgumentException) 135 { 136 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EncodingNotSupported(name)); 137 } 138 } 139 } 140 141 ReadMediaTypeAndSubtype(string text, ref int textIndex, out string type, out string subType)142 private static void ReadMediaTypeAndSubtype(string text, ref int textIndex, out string type, out string subType) 143 { 144 Debug.Assert(text != null, "text != null"); 145 int textStart = textIndex; 146 if (ReadToken(text, ref textIndex)) 147 { 148 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeUnspecified); 149 } 150 151 if (text[textIndex] != '/') 152 { 153 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSlash); 154 } 155 156 type = text.Substring(textStart, textIndex - textStart); 157 textIndex++; 158 159 int subTypeStart = textIndex; 160 ReadToken(text, ref textIndex); 161 162 if (textIndex == subTypeStart) 163 { 164 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSubType); 165 } 166 167 subType = text.Substring(subTypeStart, textIndex - subTypeStart); 168 } 169 ReadMediaType(string text)170 private static MediaType ReadMediaType(string text) 171 { 172 Debug.Assert(text != null, "text != null"); 173 174 string type; 175 string subType; 176 int textIndex = 0; 177 ReadMediaTypeAndSubtype(text, ref textIndex, out type, out subType); 178 179 KeyValuePair<string, string>[] parameters = null; 180 while (!SkipWhitespace(text, ref textIndex)) 181 { 182 if (text[textIndex] != ';') 183 { 184 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter); 185 } 186 187 textIndex++; 188 if (SkipWhitespace(text, ref textIndex)) 189 { 190 break; 191 } 192 193 ReadMediaTypeParameter(text, ref textIndex, ref parameters); 194 } 195 196 return new MediaType(type, subType, parameters); 197 } 198 ReadToken(string text, ref int textIndex)199 private static bool ReadToken(string text, ref int textIndex) 200 { 201 while (textIndex < text.Length && IsHttpToken(text[textIndex])) 202 { 203 textIndex++; 204 } 205 206 return (textIndex == text.Length); 207 } 208 SkipWhitespace(string text, ref int textIndex)209 private static bool SkipWhitespace(string text, ref int textIndex) 210 { 211 Debug.Assert(text != null, "text != null"); 212 Debug.Assert(text.Length >= 0, "text >= 0"); 213 Debug.Assert(textIndex <= text.Length, "text <= text.Length"); 214 215 while (textIndex < text.Length && Char.IsWhiteSpace(text, textIndex)) 216 { 217 textIndex++; 218 } 219 220 return (textIndex == text.Length); 221 } 222 ReadMediaTypeParameter(string text, ref int textIndex, ref KeyValuePair<string, string>[] parameters)223 private static void ReadMediaTypeParameter(string text, ref int textIndex, ref KeyValuePair<string, string>[] parameters) 224 { 225 int startIndex = textIndex; 226 if (ReadToken(text, ref textIndex)) 227 { 228 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeMissingValue); 229 } 230 231 string parameterName = text.Substring(startIndex, textIndex - startIndex); 232 if (text[textIndex] != '=') 233 { 234 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeMissingValue); 235 } 236 237 textIndex++; 238 239 string parameterValue = ReadQuotedParameterValue(parameterName, text, ref textIndex); 240 241 if (parameters == null) 242 { 243 parameters = new KeyValuePair<string, string>[1]; 244 } 245 else 246 { 247 KeyValuePair<string, string>[] grow = new KeyValuePair<string, string>[parameters.Length + 1]; 248 Array.Copy(parameters, grow, parameters.Length); 249 parameters = grow; 250 } 251 252 parameters[parameters.Length - 1] = new KeyValuePair<string, string>(parameterName, parameterValue); 253 } 254 ReadQuotedParameterValue(string parameterName, string headerText, ref int textIndex)255 private static string ReadQuotedParameterValue(string parameterName, string headerText, ref int textIndex) 256 { 257 StringBuilder parameterValue = new StringBuilder(); 258 259 bool valueIsQuoted = false; 260 if (textIndex < headerText.Length) 261 { 262 if (headerText[textIndex] == '\"') 263 { 264 textIndex++; 265 valueIsQuoted = true; 266 } 267 } 268 269 while (textIndex < headerText.Length) 270 { 271 char currentChar = headerText[textIndex]; 272 273 if (currentChar == '\\' || currentChar == '\"') 274 { 275 if (!valueIsQuoted) 276 { 277 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EscapeCharWithoutQuotes(parameterName)); 278 } 279 280 textIndex++; 281 282 if (currentChar == '\"') 283 { 284 valueIsQuoted = false; 285 break; 286 } 287 288 if (textIndex >= headerText.Length) 289 { 290 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EscapeCharAtEnd(parameterName)); 291 } 292 293 currentChar = headerText[textIndex]; 294 } 295 else 296 if (!IsHttpToken(currentChar)) 297 { 298 break; 299 } 300 301 parameterValue.Append(currentChar); 302 textIndex++; 303 } 304 305 if (valueIsQuoted) 306 { 307 throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_ClosingQuoteNotFound(parameterName)); 308 } 309 310 return parameterValue.ToString(); 311 } 312 313 IsHttpSeparator(char c)314 private static bool IsHttpSeparator(char c) 315 { 316 return 317 c == '(' || c == ')' || c == '<' || c == '>' || c == '@' || 318 c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' || 319 c == '/' || c == '[' || c == ']' || c == '?' || c == '=' || 320 c == '{' || c == '}' || c == ' ' || c == '\x9'; 321 } 322 IsHttpToken(char c)323 private static bool IsHttpToken(char c) 324 { 325 return c < '\x7F' && c > '\x1F' && !IsHttpSeparator(c); 326 } 327 328 329 [DebuggerDisplay("MediaType [{type}/{subType}]")] 330 private sealed class MediaType 331 { 332 private readonly KeyValuePair<string, string>[] parameters; 333 334 private readonly string subType; 335 336 private readonly string type; 337 MediaType(string type, string subType, KeyValuePair<string, string>[] parameters)338 internal MediaType(string type, string subType, KeyValuePair<string, string>[] parameters) 339 { 340 Debug.Assert(type != null, "type != null"); 341 Debug.Assert(subType != null, "subType != null"); 342 343 this.type = type; 344 this.subType = subType; 345 this.parameters = parameters; 346 } 347 348 internal string MimeType 349 { 350 get { return this.type + "/" + this.subType; } 351 } 352 353 internal KeyValuePair<string, string>[] Parameters 354 { 355 get { return this.parameters; } 356 } 357 358 SelectEncoding()359 internal Encoding SelectEncoding() 360 { 361 if (this.parameters != null) 362 { 363 foreach (KeyValuePair<string, string> parameter in this.parameters) 364 { 365 if (String.Equals(parameter.Key, XmlConstants.HttpCharsetParameter, StringComparison.OrdinalIgnoreCase)) 366 { 367 string encodingName = parameter.Value.Trim(); 368 if (encodingName.Length > 0) 369 { 370 return EncodingFromName(parameter.Value); 371 } 372 } 373 } 374 } 375 376 if (String.Equals(this.type, XmlConstants.MimeTextType, StringComparison.OrdinalIgnoreCase)) 377 { 378 if (String.Equals(this.subType, XmlConstants.MimeXmlSubType, StringComparison.OrdinalIgnoreCase)) 379 { 380 return null; 381 } 382 else 383 { 384 return MissingEncoding; 385 } 386 } 387 else if (String.Equals(this.type, XmlConstants.MimeApplicationType, StringComparison.OrdinalIgnoreCase) && 388 String.Equals(this.subType, XmlConstants.MimeJsonSubType, StringComparison.OrdinalIgnoreCase)) 389 { 390 return FallbackEncoding; 391 } 392 else 393 { 394 return null; 395 } 396 } 397 } 398 } 399 } 400