1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2 
3 using System.Collections.Generic;
4 using System.Collections.ObjectModel;
5 using System.Globalization;
6 using System.Linq;
7 using System.Net.Http.Formatting;
8 using System.Net.Http.Headers;
9 using System.Text;
10 using System.Xml;
11 using Newtonsoft.Json.Linq;
12 
13 namespace System.Net.Http
14 {
15     /// <summary>
16     /// Provides various internal utility functions
17     /// </summary>
18     internal static class FormattingUtilities
19     {
20         // Supported date formats for input.
21         private static readonly string[] dateFormats = new string[]
22         {
23             // "r", // RFC 1123, required output format but too strict for input
24             "ddd, d MMM yyyy H:m:s 'GMT'", // RFC 1123 (r, except it allows both 1 and 01 for date and time)
25             "ddd, d MMM yyyy H:m:s", // RFC 1123, no zone - assume GMT
26             "d MMM yyyy H:m:s 'GMT'", // RFC 1123, no day-of-week
27             "d MMM yyyy H:m:s", // RFC 1123, no day-of-week, no zone
28             "ddd, d MMM yy H:m:s 'GMT'", // RFC 1123, short year
29             "ddd, d MMM yy H:m:s", // RFC 1123, short year, no zone
30             "d MMM yy H:m:s 'GMT'", // RFC 1123, no day-of-week, short year
31             "d MMM yy H:m:s", // RFC 1123, no day-of-week, short year, no zone
32 
33             "dddd, d'-'MMM'-'yy H:m:s 'GMT'", // RFC 850
34             "dddd, d'-'MMM'-'yy H:m:s", // RFC 850 no zone
35             "ddd MMM d H:m:s yyyy", // ANSI C's asctime() format
36 
37             "ddd, d MMM yyyy H:m:s zzz", // RFC 5322
38             "ddd, d MMM yyyy H:m:s", // RFC 5322 no zone
39             "d MMM yyyy H:m:s zzz", // RFC 5322 no day-of-week
40             "d MMM yyyy H:m:s", // RFC 5322 no day-of-week, no zone
41         };
42 
43         // Valid header token characters are within the range 0x20 < c < 0x7F excluding the following characters
44         private const string NonTokenChars = "()<>@,;:\\\"/[]?={}";
45 
46         /// <summary>
47         /// The default max depth for our formatter is 256
48         /// </summary>
49         public const int DefaultMaxDepth = 256;
50 
51         /// <summary>
52         /// The default min depth for our formatter is 1
53         /// </summary>
54         public const int DefaultMinDepth = 1;
55 
56         /// <summary>
57         /// HTTP X-Requested-With header field name
58         /// </summary>
59         public const string HttpRequestedWithHeader = @"x-requested-with";
60 
61         /// <summary>
62         /// HTTP X-Requested-With header field value
63         /// </summary>
64         public const string HttpRequestedWithHeaderValue = @"xmlhttprequest";
65 
66         /// <summary>
67         /// HTTP Host header field name
68         /// </summary>
69         public const string HttpHostHeader = "Host";
70 
71         /// <summary>
72         /// HTTP Version token
73         /// </summary>
74         public const string HttpVersionToken = "HTTP";
75 
76         /// <summary>
77         /// A <see cref="Type"/> representing <see cref="UTF8Encoding"/>.
78         /// </summary>
79         public static readonly Type Utf8EncodingType = typeof(UTF8Encoding);
80 
81         /// <summary>
82         /// A <see cref="Type"/> representing <see cref="UnicodeEncoding"/>.
83         /// </summary>
84         public static readonly Type Utf16EncodingType = typeof(UnicodeEncoding);
85 
86         /// <summary>
87         /// A <see cref="Type"/> representing <see cref="HttpRequestMessage"/>.
88         /// </summary>
89         public static readonly Type HttpRequestMessageType = typeof(HttpRequestMessage);
90 
91         /// <summary>
92         /// A <see cref="Type"/> representing <see cref="HttpResponseMessage"/>.
93         /// </summary>
94         public static readonly Type HttpResponseMessageType = typeof(HttpResponseMessage);
95 
96         /// <summary>
97         /// A <see cref="Type"/> representing <see cref="HttpContent"/>.
98         /// </summary>
99         public static readonly Type HttpContentType = typeof(HttpContent);
100 
101         /// <summary>
102         /// A <see cref="Type"/> representing <see cref="DelegatingEnumerable{T}"/>.
103         /// </summary>
104         public static readonly Type DelegatingEnumerableGenericType = typeof(DelegatingEnumerable<>);
105 
106         /// <summary>
107         /// A <see cref="Type"/> representing <see cref="IEnumerable{T}"/>.
108         /// </summary>
109         public static readonly Type EnumerableInterfaceGenericType = typeof(IEnumerable<>);
110 
111         /// <summary>
112         /// A <see cref="Type"/> representing <see cref="IQueryable{T}"/>.
113         /// </summary>
114         public static readonly Type QueryableInterfaceGenericType = typeof(IQueryable<>);
115 
116         /// <summary>
117         /// Determines whether <paramref name="type"/> is a <see cref="JToken"/> type.
118         /// </summary>
119         /// <param name="type">The type to test.</param>
120         /// <returns>
121         ///   <c>true</c> if <paramref name="type"/> is a <see cref="JToken"/> type; otherwise, <c>false</c>.
122         /// </returns>
IsJTokenType(Type type)123         public static bool IsJTokenType(Type type)
124         {
125             return typeof(JToken).IsAssignableFrom(type);
126         }
127 
128         /// <summary>
129         /// Creates an empty <see cref="HttpContentHeaders"/> instance. The only way is to get it from a dummy
130         /// <see cref="HttpContent"/> instance.
131         /// </summary>
132         /// <returns>The created instance.</returns>
CreateEmptyContentHeaders()133         public static HttpContentHeaders CreateEmptyContentHeaders()
134         {
135             HttpContent tempContent = null;
136             HttpContentHeaders contentHeaders = null;
137             try
138             {
139                 tempContent = new StringContent(String.Empty);
140                 contentHeaders = tempContent.Headers;
141                 contentHeaders.Clear();
142             }
143             finally
144             {
145                 // We can dispose the content without touching the headers
146                 if (tempContent != null)
147                 {
148                     tempContent.Dispose();
149                 }
150             }
151 
152             return contentHeaders;
153         }
154 
155         /// <summary>
156         /// Ensure the actual collection is identical to the expected one
157         /// </summary>
158         /// <param name="actual">The actual collection of the instance</param>
159         /// <param name="expected">The expected collection of the instance</param>
160         /// <returns>Returns true if they are identical</returns>
ValidateCollection(Collection<MediaTypeHeaderValue> actual, MediaTypeHeaderValue[] expected)161         public static bool ValidateCollection(Collection<MediaTypeHeaderValue> actual, MediaTypeHeaderValue[] expected)
162         {
163             if (actual.Count != expected.Length)
164             {
165                 return false;
166             }
167 
168             foreach (MediaTypeHeaderValue value in expected)
169             {
170                 if (!actual.Contains(value))
171                 {
172                     return false;
173                 }
174             }
175 
176             return true;
177         }
178 
179         /// <summary>
180         /// Create a default reader quotas with a default depth quota of 1K
181         /// </summary>
182         /// <returns></returns>
CreateDefaultReaderQuotas()183         public static XmlDictionaryReaderQuotas CreateDefaultReaderQuotas()
184         {
185             return new XmlDictionaryReaderQuotas()
186             {
187                 MaxArrayLength = Int32.MaxValue,
188                 MaxBytesPerRead = Int32.MaxValue,
189                 MaxDepth = DefaultMaxDepth,
190                 MaxNameTableCharCount = Int32.MaxValue,
191                 MaxStringContentLength = Int32.MaxValue
192             };
193         }
194 
195         /// <summary>
196         /// Remove bounding quotes on a token if present
197         /// </summary>
198         /// <param name="token">Token to unquote.</param>
199         /// <returns>Unquoted token.</returns>
UnquoteToken(string token)200         public static string UnquoteToken(string token)
201         {
202             if (String.IsNullOrWhiteSpace(token))
203             {
204                 return token;
205             }
206 
207             if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)
208             {
209                 return token.Substring(1, token.Length - 2);
210             }
211 
212             return token;
213         }
214 
ValidateHeaderToken(string token)215         public static bool ValidateHeaderToken(string token)
216         {
217             return token != null && !token.Any(c => c < 0x21 || c > 0x7E || NonTokenChars.IndexOf(c) != -1);
218         }
219 
DateToString(DateTimeOffset dateTime)220         public static string DateToString(DateTimeOffset dateTime)
221         {
222             // Format according to RFC1123; 'r' uses invariant info (DateTimeFormatInfo.InvariantInfo)
223             return dateTime.ToUniversalTime().ToString("r", CultureInfo.InvariantCulture);
224         }
225 
TryParseDate(string input, out DateTimeOffset result)226         public static bool TryParseDate(string input, out DateTimeOffset result)
227         {
228             return DateTimeOffset.TryParseExact(input, dateFormats, DateTimeFormatInfo.InvariantInfo,
229                                                 DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal,
230                                                 out result);
231         }
232 
233         /// <summary>
234         /// Parses valid integer strings with no leading signs, whitespace or other <see cref="NumberStyles"/>
235         /// </summary>
236         /// <param name="value">The value to parse</param>
237         /// <param name="result">The result</param>
238         /// <returns>True if value was valid; false otherwise.</returns>
TryParseInt32(string value, out int result)239         public static bool TryParseInt32(string value, out int result)
240         {
241             return Int32.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
242         }
243     }
244 }
245