1 //------------------------------------------------------------------------------
2 // <copyright file="HttpCapabilitiesEvaluator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
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
;
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
{
49 return _userAgentCacheKeyLength
;
52 _userAgentCacheKeyLength
= value;
56 public Type ResultType
{
65 public TimeSpan CacheTime
{
74 internal string BrowserCapabilitiesProviderType
{
76 return _browserCapabilitiesProviderType
;
79 _browserCapabilitiesProviderType
= value;
83 internal HttpCapabilitiesProvider BrowserCapabilitiesProvider
{
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
;
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) {
119 _rule
= parent
._rule
;
121 if (parent
._variables
== null)
124 _variables
= new Hashtable(parent
._variables
);
126 _cachetime
= parent
._cachetime
;
127 _resultType
= parent
._resultType
;
130 AddDependency(String
.Empty
);
133 internal BrowserCapabilitiesFactoryBase BrowserCapFactory
{
135 return BrowserCapabilitiesCompiler
.BrowserCapabilitiesFactory
;
140 // remove inheritance for <result inherit="false" />
142 internal void ClearParent() {
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)
167 ruleList
.Insert(0, _rule
);
169 _rule
= new CapabilitiesSection(CapabilitiesRule
.Filter
, null, null, ruleList
);
172 internal static string GetUserAgent(HttpRequest request
) {
175 if (request
.ClientTarget
.Length
> 0) {
176 userAgent
= GetUserAgentFromClientTarget(
177 request
.Context
.ConfigurationPath
, request
.ClientTarget
);
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);
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
));
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) {
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
)) {
228 string value = (String
)result
.Capabilities
[attribute
];
230 builder
.Append(attribute
);
232 builder
.Append(value);
236 hashKey
+= builder
.ToString().GetHashCode().ToString(CultureInfo
.InvariantCulture
);
238 HttpCapabilitiesBase newResult
= cacheInternal
.Get(hashKey
) as HttpCapabilitiesBase
;
239 if (newResult
!= null) {
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) {
284 // 1.1) optimistic cache entry could tell us to do full cache lookup
286 if (optimisticCacheResult
== _disableOptimisticCachingSingleton
) {
287 doFullCacheKeyLookup
= true;
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 }
);
310 // We've never seen the UA before (parse all headers to
311 // determine if the new UA also carries modile device
314 // It's a mobile UA (so parse all headers) and do full
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
;
329 if (key
.Length
== 0) {
333 value = request
.ServerVariables
[key
];
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
;
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 }
);
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;
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
);