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