release capabilities file after use
[mono.git] / mcs / class / System.Web / System.Web / CapabilitiesLoader.cs
1 // 
2 // System.Web.CapabilitiesLoader
3 //
4 // Loads data from browscap.ini file provided by Gary J. Keith from
5 // http://www.GaryKeith.com/browsers. Please don't abuse the
6 // site when updating browscap.ini file. Use the update-browscap.exe tool.
7 //
8 // Authors:
9 //   Gonzalo Paniagua Javier (gonzalo@ximian.com)
10 //
11 // (c) 2003 Novell, Inc. (http://www.novell.com)
12 //
13
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 using System;
36 using System.Collections;
37 using System.Collections.Specialized;
38 using System.Globalization;
39 using System.IO;
40 using System.Text.RegularExpressions;
41 using System.Web.Configuration;
42
43 namespace System.Web
44 {
45         class BrowserData
46         {
47                 static char [] wildchars = new char [] {'*', '?'};
48
49                 object this_lock = new object ();
50                 BrowserData parent;
51                 string text;
52                 string pattern;
53 #if TARGET_JVM
54                 java.util.regex.Pattern regex;
55 #else
56                 Regex regex;
57 #endif
58                 ListDictionary data;
59
60                 public BrowserData (string pattern)
61                 {
62                         int norx = pattern.IndexOfAny (wildchars);
63                         if (norx == -1) {
64                                 text = pattern;
65                         } else {
66                                 this.pattern = pattern.Substring (norx);
67                                 text = pattern.Substring (0, norx);
68                                 if (text.Length == 0)
69                                         text = null;
70
71                                 this.pattern = this.pattern.Replace (".", "\\.");
72                                 this.pattern = this.pattern.Replace ("(", "\\(");
73                                 this.pattern = this.pattern.Replace (")", "\\)");
74                                 this.pattern = this.pattern.Replace ("[", "\\[");
75                                 this.pattern = this.pattern.Replace ("]", "\\]");
76                                 this.pattern = this.pattern.Replace ("?", ".");
77                                 this.pattern = this.pattern.Replace ("*", ".*");
78                         }
79                 }
80
81                 public BrowserData Parent {
82                         get { return parent; }
83                         set { parent = value; }
84                 }
85
86                 public void Add (string key, string value)
87                 {
88                         if (data == null)
89                                 data = new ListDictionary ();
90
91                         data.Add (key, value);
92                 }
93
94                 public Hashtable GetProperties (Hashtable tbl)
95                 {
96                         if (parent != null)
97                                 parent.GetProperties (tbl);
98
99                         if (data ["browser"] != null) { // Last one (most derived) will win.
100                                 tbl ["browser"] = data ["browser"];
101                         } else if (tbl ["browser"] == null) { // If none so far defined value set to *
102                                 tbl ["browser"] = "*";
103                         }
104
105                         if (!tbl.ContainsKey ("browsers")) {
106                                 tbl ["browsers"] = new ArrayList ();
107                         }
108
109                         ((ArrayList) tbl ["browsers"]).Add (tbl["browser"]);
110
111                         foreach (string key in data.Keys)
112                                 tbl [key.ToLower (CultureInfo.InvariantCulture).Trim ()] = data [key];
113                         
114                         return tbl;
115                 }
116                 
117                 public string GetParentName ()
118                 {
119                         return (string)(data.Contains("parent")? data ["parent"] : null);
120                 }
121                 
122                 public string GetAlternateBrowser ()
123                 {
124                         return (pattern == null) ? text : null;
125                 }
126
127                 public string GetBrowser ()
128                 {
129                         if (pattern == null)
130                                 return text;
131
132                         return (string) data ["browser"];
133                 }
134                 
135                 public bool IsMatch (string expression)
136                 {
137                         if (expression == null || expression.Length == 0)
138                                 return false;
139
140                         if (text != null) {
141                                 if (text [0] != expression [0] ||
142                                     String.Compare (text, 1, expression, 1,
143                                                     text.Length - 1, false,
144                                                     CultureInfo.InvariantCulture) != 0) {
145                                         return false;
146                                 }
147                                 expression = expression.Substring (text.Length);
148                         }
149                         
150                         if (pattern == null)
151                                 return expression.Length == 0;
152
153                         lock (this_lock) {
154                                 if (regex == null)
155 #if TARGET_JVM
156                                         regex = java.util.regex.Pattern.compile (pattern);
157 #else
158                                 regex = new Regex (pattern);
159 #endif
160                         }
161 #if TARGET_JVM
162                         return regex.matcher ((java.lang.CharSequence) (object) expression).matches ();
163 #else
164                         return regex.Match (expression).Success;
165 #endif
166                 }
167         }
168         
169         class CapabilitiesLoader : MarshalByRefObject
170         {
171                 const int userAgentsCacheSize = 3000;
172                 static Hashtable defaultCaps;
173                 static readonly object lockobj = new object ();
174
175 #if TARGET_JVM
176                 static bool loaded {
177                         get {
178                                 return alldata != null;
179                         }
180                         set {
181                                 if (alldata == null)
182                                         alldata = new ArrayList ();
183                         }
184                 }
185
186                 const string alldataKey = "System.Web.CapabilitiesLoader.alldata";
187                 static ICollection alldata {
188                         get {
189                                 return (ICollection) AppDomain.CurrentDomain.GetData (alldataKey);
190                         }
191                         set {
192                                 AppDomain.CurrentDomain.SetData (alldataKey, value);
193                         }
194                 }
195
196                 const string userAgentsCacheKey = "System.Web.CapabilitiesLoader.userAgentsCache";
197                 static Hashtable userAgentsCache {
198                         get {
199                                 lock (typeof (CapabilitiesLoader)) {
200                                         Hashtable agentsCache = (Hashtable) AppDomain.CurrentDomain.GetData (userAgentsCacheKey);
201                                         if (agentsCache == null) {
202                                                 agentsCache = Hashtable.Synchronized (new Hashtable (userAgentsCacheSize + 10));
203                                                 AppDomain.CurrentDomain.SetData (userAgentsCacheKey, agentsCache);
204                                         }
205
206                                         return agentsCache;
207                                 }
208                         }
209                 }
210 #else
211                 static volatile bool loaded;
212                 static ICollection alldata;
213                 static Hashtable userAgentsCache = Hashtable.Synchronized(new Hashtable(userAgentsCacheSize+10));
214 #endif
215
216                 private CapabilitiesLoader () {}
217
218                 static CapabilitiesLoader ()
219                 {
220                         defaultCaps = new Hashtable ();
221                         defaultCaps.Add ("activexcontrols", "False");
222                         defaultCaps.Add ("alpha", "False");
223                         defaultCaps.Add ("aol", "False");
224                         defaultCaps.Add ("aolversion", "0");
225                         defaultCaps.Add ("authenticodeupdate", "");
226                         defaultCaps.Add ("backgroundsounds", "False");
227                         defaultCaps.Add ("beta", "False");
228                         defaultCaps.Add ("browser", "*");
229                         defaultCaps.Add ("browsers", new ArrayList ());
230                         defaultCaps.Add ("cdf", "False");
231                         defaultCaps.Add ("clrversion", "0");
232                         defaultCaps.Add ("cookies", "False");
233                         defaultCaps.Add ("crawler", "False");
234                         defaultCaps.Add ("css", "0");
235                         defaultCaps.Add ("cssversion", "0");
236                         defaultCaps.Add ("ecmascriptversion", "0.0");
237                         defaultCaps.Add ("frames", "False");
238                         defaultCaps.Add ("iframes", "False");
239                         defaultCaps.Add ("isbanned", "False");
240                         defaultCaps.Add ("ismobiledevice", "False");
241                         defaultCaps.Add ("issyndicationreader", "False");
242                         defaultCaps.Add ("javaapplets", "False");
243                         defaultCaps.Add ("javascript", "False");
244                         defaultCaps.Add ("majorver", "0");
245                         defaultCaps.Add ("minorver", "0");
246                         defaultCaps.Add ("msdomversion", "0.0");
247                         defaultCaps.Add ("netclr", "False");
248                         defaultCaps.Add ("platform", "unknown");
249                         defaultCaps.Add ("stripper", "False");
250                         defaultCaps.Add ("supportscss", "False");
251                         defaultCaps.Add ("tables", "False");
252                         defaultCaps.Add ("vbscript", "False");
253                         defaultCaps.Add ("version", "0");
254                         defaultCaps.Add ("w3cdomversion", "0.0");
255                         defaultCaps.Add ("wap", "False");
256                         defaultCaps.Add ("win16", "False");
257                         defaultCaps.Add ("win32", "False");
258                         defaultCaps.Add ("win64", "False");
259
260 #if NET_2_0
261                         defaultCaps.Add ("adapters", new Hashtable ());
262                         defaultCaps.Add ("cancombineformsindeck", "False");
263                         defaultCaps.Add ("caninitiatevoicecall", "False");
264                         defaultCaps.Add ("canrenderafterinputorselectelement", "False");
265                         defaultCaps.Add ("canrenderemptyselects", "False");
266                         defaultCaps.Add ("canrenderinputandselectelementstogether", "False");
267                         defaultCaps.Add ("canrendermixedselects", "False");
268                         defaultCaps.Add ("canrenderoneventandprevelementstogether", "False");
269                         defaultCaps.Add ("canrenderpostbackcards", "False");
270                         defaultCaps.Add ("canrendersetvarzerowithmultiselectionlist", "False");
271                         defaultCaps.Add ("cansendmail", "False");
272                         defaultCaps.Add ("defaultsubmitbuttonlimit", "0");
273                         defaultCaps.Add ("gatewayminorversion", "0");
274                         defaultCaps.Add ("gatewaymajorversion", "0");
275                         defaultCaps.Add ("gatewayversion", "None");
276                         defaultCaps.Add ("hasbackbutton", "True");
277                         defaultCaps.Add ("hidesrightalignedmultiselectscrollbars", "False");
278                         defaultCaps.Add ("inputtype", "telephoneKeypad");
279                         defaultCaps.Add ("iscolor", "False");
280                         defaultCaps.Add ("jscriptversion", "0.0");
281                         defaultCaps.Add ("maximumhreflength", "0");
282                         defaultCaps.Add ("maximumrenderedpagesize", "2000");
283                         defaultCaps.Add ("maximumsoftkeylabellength", "5");
284                         defaultCaps.Add ("minorversionstring", "0.0");
285                         defaultCaps.Add ("mobiledevicemanufacturer", "Unknown");
286                         defaultCaps.Add ("mobiledevicemodel", "Unknown");
287                         defaultCaps.Add ("numberofsoftkeys", "0");
288                         defaultCaps.Add ("preferredimagemime", "image/gif");
289                         defaultCaps.Add ("preferredrenderingmime", "text/html");
290                         defaultCaps.Add ("preferredrenderingtype", "html32");
291                         defaultCaps.Add ("preferredrequestencoding", "");
292                         defaultCaps.Add ("preferredresponseencoding", "");
293                         defaultCaps.Add ("rendersbreakbeforewmlselectandinput", "False");
294                         defaultCaps.Add ("rendersbreaksafterhtmllists", "True");
295                         defaultCaps.Add ("rendersbreaksafterwmlanchor", "False");
296                         defaultCaps.Add ("rendersbreaksafterwmlinput", "False");
297                         defaultCaps.Add ("renderswmldoacceptsinline", "True");
298                         defaultCaps.Add ("renderswmlselectsasmenucards", "False");
299                         defaultCaps.Add ("requiredmetatagnamevalue", "");
300                         defaultCaps.Add ("requiresattributecolonsubstitution", "False");
301                         defaultCaps.Add ("requirescontenttypemetatag", "False");
302                         defaultCaps.Add ("requirescontrolstateinsession", "False");
303                         defaultCaps.Add ("requiresdbcscharacter", "False");
304                         defaultCaps.Add ("requireshtmladaptiveerrorreporting", "False");
305                         defaultCaps.Add ("requiresleadingpagebreak", "False");
306                         defaultCaps.Add ("requiresnobreakinformatting", "False");
307                         defaultCaps.Add ("requiresoutputoptimization", "False");
308                         defaultCaps.Add ("requiresphonenumbersasplaintext", "False");
309                         defaultCaps.Add ("requiresspecialviewstateencoding", "False");
310                         defaultCaps.Add ("requiresuniquefilepathsuffix", "False");
311                         defaultCaps.Add ("requiresuniquehtmlcheckboxnames", "False");
312                         defaultCaps.Add ("requiresuniquehtmlinputnames", "False");
313                         defaultCaps.Add ("requiresurlencodedpostfieldvalues", "False");
314                         defaultCaps.Add ("screenbitdepth", "1");
315                         defaultCaps.Add ("screencharactersheight", "6");
316                         defaultCaps.Add ("screencharacterswidth", "12");
317                         defaultCaps.Add ("screenpixelsheight", "72");
318                         defaultCaps.Add ("screenpixelswidth", "96");
319                         defaultCaps.Add ("supportsaccesskeyattribute", "False");
320                         defaultCaps.Add ("supportsbodycolor", "True");
321                         defaultCaps.Add ("supportsbold", "False");
322                         defaultCaps.Add ("supportscachecontrolmetatag", "True");
323                         defaultCaps.Add ("supportscallback", "False");
324                         defaultCaps.Add ("supportsdivalign", "True");
325                         defaultCaps.Add ("supportsdivnowrap", "False");
326                         defaultCaps.Add ("supportsemptystringincookievalue", "False");
327                         defaultCaps.Add ("supportsfontcolor", "True");
328                         defaultCaps.Add ("supportsfontname", "False");
329                         defaultCaps.Add ("supportsfontsize", "False");
330                         defaultCaps.Add ("supportsimagesubmit", "False");
331                         defaultCaps.Add ("supportsimodesymbols", "False");
332                         defaultCaps.Add ("supportsinputistyle", "False");
333                         defaultCaps.Add ("supportsinputmode", "False");
334                         defaultCaps.Add ("supportsitalic", "False");
335                         defaultCaps.Add ("supportsjphonemultimediaattributes", "False");
336                         defaultCaps.Add ("supportsjphonesymbols", "False");
337                         defaultCaps.Add ("supportsquerystringinformaction", "True");
338                         defaultCaps.Add ("supportsredirectwithcookie", "True");
339                         defaultCaps.Add ("supportsselectmultiple", "True");
340                         defaultCaps.Add ("supportsuncheck", "True");
341                         defaultCaps.Add ("supportsxmlhttp", "False");
342                         defaultCaps.Add ("type", "Unknown");
343 #endif
344                 }
345                 
346                 public static Hashtable GetCapabilities (string userAgent)
347                 {
348                         Init ();
349                         if (userAgent != null)
350                                 userAgent = userAgent.Trim ();
351
352                         if (alldata == null || userAgent == null || userAgent == "")
353                                 return defaultCaps;
354
355                         Hashtable userBrowserCaps = (Hashtable) (userAgentsCache.Contains(userAgent)? userAgentsCache [userAgent] : null);
356                         if (userBrowserCaps == null) {
357                                 foreach (BrowserData bd in alldata) {
358                                         if (bd.IsMatch (userAgent)) {
359                                                 Hashtable tbl;
360                                                 tbl = new Hashtable (defaultCaps);
361                                                 userBrowserCaps = bd.GetProperties (tbl);
362                                                 break;
363                                         }
364                                 }
365
366                                 if (userBrowserCaps == null)
367                                         userBrowserCaps = defaultCaps;
368
369                                 lock (typeof (CapabilitiesLoader)) {
370                                         if (userAgentsCache.Count >= userAgentsCacheSize)
371                                                 userAgentsCache.Clear ();
372                                 }
373                                 userAgentsCache [userAgent] = userBrowserCaps;
374                         }
375                         return userBrowserCaps;
376                 }
377
378                 static void Init ()
379                 {
380                         if (loaded)
381                                 return;
382
383                         lock (lockobj) {
384                                 if (loaded)
385                                         return;
386 #if TARGET_J2EE
387                                 string filepath = "browscap.ini";
388 #else
389                                 string dir = HttpRuntime.MachineConfigurationDirectory;
390                                 string filepath = Path.Combine (dir, "browscap.ini");
391                                 if (!File.Exists (filepath)) {
392                                         // try removing the trailing version directory
393                                         dir = Path.GetDirectoryName (dir);
394                                         filepath = Path.Combine (dir, "browscap.ini");
395                                 }
396 #endif
397                                 try {
398                                         LoadFile (filepath);
399                                 } catch (Exception) {}
400
401                                 loaded = true;
402                         }
403                 }
404
405 #if TARGET_J2EE
406                 private static TextReader GetJavaTextReader(string filename)
407                 {
408                         try
409                         {
410                                 java.lang.ClassLoader cl = (java.lang.ClassLoader)
411                                         AppDomain.CurrentDomain.GetData("GH_ContextClassLoader");
412                                 if (cl == null)
413                                         return null;
414
415                                 string custom = String.Concat("browscap/", filename);
416                                 
417                                 java.io.InputStream inputStream = cl.getResourceAsStream(custom);
418                                 if (inputStream == null)
419                                         inputStream = cl.getResourceAsStream(filename);
420
421                                 if (inputStream == null)
422                                         return null;
423
424                                 return new StreamReader (new System.Web.J2EE.J2EEUtils.InputStreamWrapper (inputStream));
425                         }
426                         catch (Exception e)
427                         {
428                                 return null;
429                         }
430                 }
431 #endif
432
433                 static void LoadFile (string filename)
434                 {
435 #if TARGET_J2EE
436                         TextReader input = GetJavaTextReader(filename);
437                         if(input == null)
438                                 return;
439 #else
440                         if (!File.Exists (filename))
441                                 return;
442
443                         TextReader input = new StreamReader (File.OpenRead (filename));
444 #endif
445                         using (input) {
446                         string str;
447                         Hashtable allhash = new Hashtable ();
448                         int aux = 0;
449                         ArrayList browserData = new ArrayList ();
450                         while ((str = input.ReadLine ()) != null) {
451                                 if (str.Length == 0 || str [0] == ';')
452                                         continue;
453
454                                 string userAgent = str.Substring (1, str.Length - 2);
455                                 BrowserData data = new BrowserData (userAgent);
456                                 ReadCapabilities (input, data);
457
458                                 /* Ignore default browser and file version information */
459                                 if (userAgent == "*" || userAgent == "GJK_Browscap_Version")
460                                         continue;
461
462                                 string key = data.GetBrowser ();
463                                 if (key == null || allhash.ContainsKey (key)) {
464                                         allhash.Add (aux++, data);
465                                         browserData.Add (data);
466                                 } else {
467                                         allhash.Add (key, data);
468                                         browserData.Add (data);
469                                 }
470                         }                       
471
472                         alldata = browserData;
473                         foreach (BrowserData data in alldata) {
474                                 string pname = data.GetParentName ();
475                                 if (pname == null)
476                                         continue;
477
478                                 data.Parent = (BrowserData) allhash [pname];
479                         }
480                         }
481                 }
482
483                 static char [] eq = new char []{'='};
484                 static void ReadCapabilities (TextReader input, BrowserData data)
485                 {
486                         string str, key;
487                         string [] keyvalue;
488                         
489                         while ((str = input.ReadLine ()) != null && str.Length != 0) {
490                                 keyvalue = str.Split (eq, 2);
491                                 key = keyvalue [0].ToLower (CultureInfo.InvariantCulture).Trim ();
492                                 if (key.Length == 0)
493                                         continue;
494                                 data.Add (key, keyvalue [1]);
495                         }
496                 }
497         }
498 }