Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Web / Configuration / HttpCapabilitiesEvaluator.cs
blob30e5d5999b9ecc4130a309097c71159b0a04a959
1 //------------------------------------------------------------------------------
2 // <copyright file="HttpCapabilitiesEvaluator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
7 namespace System.Web.Configuration {
9 using System.Collections;
10 using System.Collections.Specialized;
11 using System.Diagnostics;
12 using System.Globalization;
13 using System.Reflection;
14 using System.Security;
15 using System.Text;
16 using System.Text.RegularExpressions;
17 using System.Threading;
18 using System.Web.Caching;
19 using System.Web.Compilation;
20 using System.Web.Hosting;
21 using System.Security.Permissions;
23 public abstract class HttpCapabilitiesProvider {
24 public abstract HttpBrowserCapabilities GetBrowserCapabilities(HttpRequest request);
28 // CapabilitiesEvaluator encapabilitiesulates a set of rules for deducing
29 // a capabilities object from an HttpRequest
31 public class HttpCapabilitiesDefaultProvider : HttpCapabilitiesProvider {
33 internal CapabilitiesRule _rule;
34 internal Hashtable _variables;
35 internal Type _resultType;
36 internal TimeSpan _cachetime;
37 internal string _cacheKeyPrefix;
39 private int _userAgentCacheKeyLength;
40 private static int _idCounter;
41 private const string _isMobileDeviceCapKey = "isMobileDevice";
42 private static object _disableOptimisticCachingSingleton = new object();
43 private const int _defaultUserAgentCacheKeyLength = 64;
44 private string _browserCapabilitiesProviderType = null;
45 private HttpCapabilitiesProvider _browserCapabilitiesProvider = null;
47 public int UserAgentCacheKeyLength {
48 get {
49 return _userAgentCacheKeyLength;
51 set {
52 _userAgentCacheKeyLength = value;
56 public Type ResultType {
57 get {
58 return _resultType;
60 set {
61 _resultType = value;
65 public TimeSpan CacheTime {
66 get {
67 return _cachetime;
69 set {
70 _cachetime = value;
74 internal string BrowserCapabilitiesProviderType {
75 get {
76 return _browserCapabilitiesProviderType;
78 set {
79 _browserCapabilitiesProviderType = value;
83 internal HttpCapabilitiesProvider BrowserCapabilitiesProvider {
84 get {
85 if (_browserCapabilitiesProvider == null) {
86 if (BrowserCapabilitiesProviderType != null) {
87 Type t = System.Type.GetType(BrowserCapabilitiesProviderType, true, true);
88 _browserCapabilitiesProvider = (HttpCapabilitiesProvider)Activator.CreateInstance(t);
91 return _browserCapabilitiesProvider;
93 set {
94 _browserCapabilitiesProvider = value;
98 public HttpCapabilitiesDefaultProvider() : this(RuntimeConfig.GetAppConfig().BrowserCaps){
99 if (RuntimeConfig.GetAppConfig().BrowserCaps != null) {
100 _userAgentCacheKeyLength = RuntimeConfig.GetAppConfig().BrowserCaps.UserAgentCacheKeyLength;
102 if (_userAgentCacheKeyLength == 0) {
103 _userAgentCacheKeyLength = _defaultUserAgentCacheKeyLength;
108 // internal constructor; inherit from parent
110 public HttpCapabilitiesDefaultProvider(HttpCapabilitiesDefaultProvider parent) {
111 int id = Interlocked.Increment(ref _idCounter);
112 // don't do id.ToString() on every request, do it here
113 _cacheKeyPrefix = CacheInternal.PrefixHttpCapabilities + id.ToString(CultureInfo.InvariantCulture);
115 if (parent == null) {
116 ClearParent();
118 else {
119 _rule = parent._rule;
121 if (parent._variables == null)
122 _variables = null;
123 else
124 _variables = new Hashtable(parent._variables);
126 _cachetime = parent._cachetime;
127 _resultType = parent._resultType;
130 AddDependency(String.Empty);
133 internal BrowserCapabilitiesFactoryBase BrowserCapFactory {
134 get {
135 return BrowserCapabilitiesCompiler.BrowserCapabilitiesFactory;
140 // remove inheritance for <result inherit="false" />
142 internal void ClearParent() {
143 _rule = null;
144 _cachetime = TimeSpan.FromSeconds(60); // one minute default expiry
145 _variables = new Hashtable();
146 _resultType = typeof(HttpCapabilitiesBase);
150 // add a dependency when we encounter a <use var="HTTP_ACCEPT_LANGUAGE" as="lang" />
152 public void AddDependency(String variable) {
153 if (variable.Equals("HTTP_USER_AGENT"))
154 variable = String.Empty;
156 _variables[variable] = true;
160 // sets the set of rules
162 public virtual void AddRuleList(ArrayList ruleList) {
163 if (ruleList.Count == 0)
164 return;
166 if (_rule != null)
167 ruleList.Insert(0, _rule);
169 _rule = new CapabilitiesSection(CapabilitiesRule.Filter, null, null, ruleList);
172 internal static string GetUserAgent(HttpRequest request) {
173 string userAgent;
175 if (request.ClientTarget.Length > 0) {
176 userAgent = GetUserAgentFromClientTarget(
177 request.Context.ConfigurationPath, request.ClientTarget);
179 else {
180 userAgent = request.UserAgent;
183 // Protect against attacks with long User-Agent headers
184 if (userAgent != null && userAgent.Length > 512) {
185 userAgent = userAgent.Substring(0, 512);
188 return userAgent;
191 internal static string GetUserAgentFromClientTarget(VirtualPath configPath, string clientTarget) {
193 // Lookup ClientTarget section in config.
194 ClientTargetSection clientTargetConfig = RuntimeConfig.GetConfig(configPath).ClientTarget;
196 string userAgent = null;
198 if ( clientTargetConfig.ClientTargets[ clientTarget ] != null )
200 userAgent = clientTargetConfig.ClientTargets[ clientTarget ].UserAgent;
203 if ( userAgent == null )
205 throw new HttpException(SR.GetString(SR.Invalid_client_target, clientTarget));
208 return userAgent;
211 private void CacheBrowserCapResult(ref HttpCapabilitiesBase result) {
212 // Use the previously cached browserCap object if an identical
213 // browserCap is found.
214 CacheStoreProvider cacheInternal = System.Web.HttpRuntime.Cache.InternalCache;
216 if (result.Capabilities == null) {
217 return;
220 string hashKey = CacheInternal.PrefixBrowserCapsHash;
221 StringBuilder builder = new StringBuilder();
222 foreach (string attribute in result.Capabilities.Keys) {
223 // Ignore useragent that is stored with empty key.
224 if (String.IsNullOrEmpty(attribute)) {
225 continue;
228 string value = (String)result.Capabilities[attribute];
229 if (value != null) {
230 builder.Append(attribute);
231 builder.Append("$");
232 builder.Append(value);
233 builder.Append("$");
236 hashKey += builder.ToString().GetHashCode().ToString(CultureInfo.InvariantCulture);
238 HttpCapabilitiesBase newResult = cacheInternal.Get(hashKey) as HttpCapabilitiesBase;
239 if (newResult != null) {
240 result = newResult;
242 else {
243 // cache it and respect cachetime
244 cacheInternal.Insert(hashKey, result, new CacheInsertOptions() { SlidingExpiration = _cachetime });
248 public override HttpBrowserCapabilities GetBrowserCapabilities(HttpRequest request) {
249 return (HttpBrowserCapabilities)Evaluate(request);
253 // Actually computes the browser capabilities
255 internal HttpCapabilitiesBase Evaluate(HttpRequest request) {
257 HttpCapabilitiesBase result;
258 CacheStoreProvider cacheInternal = System.Web.HttpRuntime.Cache.InternalCache;
261 // 1) grab UA and do optimistic cache lookup (if UA is in dependency list)
263 string userAgent = GetUserAgent(request);
264 string userAgentCacheKey = userAgent;
266 // Use the shorten userAgent as the cache key.
267 Debug.Assert(UserAgentCacheKeyLength != 0);
268 // Trim the useragent string based on <browserCaps> config
269 if (userAgentCacheKey != null && userAgentCacheKey.Length > UserAgentCacheKeyLength) {
270 userAgentCacheKey = userAgentCacheKey.Substring(0, UserAgentCacheKeyLength);
273 bool doFullCacheKeyLookup = false;
274 string optimisticCacheKey = _cacheKeyPrefix + userAgentCacheKey;
275 object optimisticCacheResult = cacheInternal.Get(optimisticCacheKey);
277 // optimize for common case (desktop browser)
278 result = optimisticCacheResult as HttpCapabilitiesBase;
279 if (result != null) {
280 return result;
284 // 1.1) optimistic cache entry could tell us to do full cache lookup
286 if (optimisticCacheResult == _disableOptimisticCachingSingleton) {
287 doFullCacheKeyLookup = true;
289 else {
290 // cache it and respect _cachetime
291 result = EvaluateFinal(request, true);
293 // Optimized cache key is disabled if the request matches any headers defined within identifications.
294 if (result.UseOptimizedCacheKey) {
296 // Use the same browserCap result if one with the same capabilities can be found in the cache.
297 // This is to reduce the number of identical browserCap instances being cached.
298 CacheBrowserCapResult(ref result);
300 // Cache the result using the optimisicCacheKey
301 cacheInternal.Insert(optimisticCacheKey, result, new CacheInsertOptions() { SlidingExpiration = _cachetime });
303 return result;
308 // 2) either:
310 // We've never seen the UA before (parse all headers to
311 // determine if the new UA also carries modile device
312 // httpheaders).
314 // It's a mobile UA (so parse all headers) and do full
315 // cache lookup
317 // UA isn't in dependency list (customer custom caps section)
320 IDictionaryEnumerator de = _variables.GetEnumerator();
321 StringBuilder sb = new StringBuilder(_cacheKeyPrefix);
323 InternalSecurityPermissions.AspNetHostingPermissionLevelLow.Assert();
325 while (de.MoveNext()) {
326 string key = (string)de.Key;
327 string value;
329 if (key.Length == 0) {
330 value = userAgent;
332 else {
333 value = request.ServerVariables[key];
336 if (value != null) {
337 sb.Append(value);
341 CodeAccessPermission.RevertAssert();
343 sb.Append(BrowserCapabilitiesFactoryBase.GetBrowserCapKey(BrowserCapFactory.InternalGetMatchedHeaders(), request));
344 string fullCacheKey = sb.ToString();
347 // Only do full cache lookup if the optimistic cache
348 // result was _disableOptimisticCachingSingleton or
349 // if UserAgent wasn't in the cap var list.
351 if (userAgent == null || doFullCacheKeyLookup) {
353 result = cacheInternal.Get(fullCacheKey) as HttpCapabilitiesBase;
355 if (result != null)
356 return result;
359 result = EvaluateFinal(request, false);
361 // Use the same browserCap result if one with the same matched nodes can be found in the cache.
362 // This is to reduce the number of identical browserCap instances being cached.
363 CacheBrowserCapResult(ref result);
365 // cache it and respect _cachetime
366 cacheInternal.Insert(fullCacheKey, result, new CacheInsertOptions() { SlidingExpiration = _cachetime });
367 if(optimisticCacheKey != null) {
368 cacheInternal.Insert(optimisticCacheKey, _disableOptimisticCachingSingleton, new CacheInsertOptions() { SlidingExpiration = _cachetime });
371 return result;
374 internal HttpCapabilitiesBase EvaluateFinal(HttpRequest request, bool onlyEvaluateUserAgent) {
375 HttpBrowserCapabilities browserCaps = BrowserCapFactory.GetHttpBrowserCapabilities(request);
376 CapabilitiesState state = new CapabilitiesState(request, browserCaps.Capabilities);
377 if (onlyEvaluateUserAgent) {
378 state.EvaluateOnlyUserAgent = true;
381 if(_rule != null) {
382 string oldIsMobileDevice = browserCaps[_isMobileDeviceCapKey];
383 browserCaps.Capabilities[_isMobileDeviceCapKey] = null;
385 _rule.Evaluate(state);
387 string newIsMobileDevice = browserCaps[_isMobileDeviceCapKey];
388 if (newIsMobileDevice == null) {
389 browserCaps.Capabilities[_isMobileDeviceCapKey] = oldIsMobileDevice;
391 else if (newIsMobileDevice.Equals("true")) {
392 browserCaps.DisableOptimizedCacheKey();
396 // create the new type
398 HttpCapabilitiesBase result = (HttpCapabilitiesBase)HttpRuntime.CreateNonPublicInstance(_resultType);
399 result.InitInternal(browserCaps);
401 return result;