Merge branch 'master'
[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-2009 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 using System.Web.Util;
43
44 namespace System.Web
45 {
46         sealed class BrowserData
47         {
48                 static char [] wildchars = new char [] {'*', '?'};
49
50                 object this_lock = new object ();
51                 BrowserData parent;
52                 string text;
53                 string pattern;
54                 Regex regex;
55                 ListDictionary data;
56
57                 public BrowserData (string pattern)
58                 {
59                         int norx = pattern.IndexOfAny (wildchars);
60                         if (norx == -1) {
61                                 text = pattern;
62                         } else {
63                                 this.pattern = pattern.Substring (norx);
64                                 text = pattern.Substring (0, norx);
65                                 if (text.Length == 0)
66                                         text = null;
67
68                                 this.pattern = this.pattern.Replace (".", "\\.");
69                                 this.pattern = this.pattern.Replace ("(", "\\(");
70                                 this.pattern = this.pattern.Replace (")", "\\)");
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                         }
76                 }
77
78                 public BrowserData Parent {
79                         get { return parent; }
80                         set { parent = value; }
81                 }
82
83                 public void Add (string key, string value)
84                 {
85                         if (data == null)
86                                 data = new ListDictionary ();
87
88                         data.Add (key, value);
89                 }
90
91                 public Hashtable GetProperties (Hashtable tbl)
92                 {
93                         if (parent != null)
94                                 parent.GetProperties (tbl);
95
96                         if (data ["browser"] != null) { // Last one (most derived) will win.
97                                 tbl ["browser"] = data ["browser"];
98                         } else if (tbl ["browser"] == null) { // If none so far defined value set to *
99                                 tbl ["browser"] = "*";
100                         }
101
102                         if (!tbl.ContainsKey ("browsers")) {
103                                 tbl ["browsers"] = new ArrayList ();
104                         }
105
106                         ((ArrayList) tbl ["browsers"]).Add (tbl["browser"]);
107
108                         foreach (string key in data.Keys)
109                                 tbl [key.ToLower (Helpers.InvariantCulture).Trim ()] = data [key];
110                         
111                         return tbl;
112                 }
113                 
114                 public string GetParentName ()
115                 {
116                         return (string)(data.Contains("parent")? data ["parent"] : null);
117                 }
118                 
119                 public string GetAlternateBrowser ()
120                 {
121                         return (pattern == null) ? text : null;
122                 }
123
124                 public string GetBrowser ()
125                 {
126                         if (pattern == null)
127                                 return text;
128
129                         return (string) data ["browser"];
130                 }
131                 
132                 public bool IsMatch (string expression)
133                 {
134                         if (expression == null || expression.Length == 0)
135                                 return false;
136
137                         if (text != null) {
138                                 if (text [0] != expression [0] ||
139                                     String.Compare (text, 1, expression, 1,
140                                                     text.Length - 1, false,
141                                                     Helpers.InvariantCulture) != 0) {
142                                         return false;
143                                 }
144                                 expression = expression.Substring (text.Length);
145                         }
146                         
147                         if (pattern == null)
148                                 return expression.Length == 0;
149
150                         lock (this_lock) {
151                                 if (regex == null)
152                                 regex = new Regex (pattern);
153                         }
154                         return regex.Match (expression).Success;
155                 }
156         }
157         
158         sealed class CapabilitiesLoader : MarshalByRefObject
159         {
160                 const int userAgentsCacheSize = 3000;
161                 static Hashtable defaultCaps;
162                 static readonly object lockobj = new object ();
163
164                 static volatile bool loaded;
165                 static ICollection alldata;
166                 static Hashtable userAgentsCache = Hashtable.Synchronized(new Hashtable(userAgentsCacheSize+10));
167
168                 CapabilitiesLoader () {}
169
170                 static CapabilitiesLoader ()
171                 {
172                         defaultCaps = new Hashtable (StringComparer.OrdinalIgnoreCase);
173                         defaultCaps.Add ("activexcontrols", "False");
174                         defaultCaps.Add ("alpha", "False");
175                         defaultCaps.Add ("aol", "False");
176                         defaultCaps.Add ("aolversion", "0");
177                         defaultCaps.Add ("authenticodeupdate", "");
178                         defaultCaps.Add ("backgroundsounds", "False");
179                         defaultCaps.Add ("beta", "False");
180                         defaultCaps.Add ("browser", "*");
181                         defaultCaps.Add ("browsers", new ArrayList ());
182                         defaultCaps.Add ("cdf", "False");
183                         defaultCaps.Add ("clrversion", "0");
184                         defaultCaps.Add ("cookies", "False");
185                         defaultCaps.Add ("crawler", "False");
186                         defaultCaps.Add ("css", "0");
187                         defaultCaps.Add ("cssversion", "0");
188                         defaultCaps.Add ("ecmascriptversion", "0.0");
189                         defaultCaps.Add ("frames", "False");
190                         defaultCaps.Add ("iframes", "False");
191                         defaultCaps.Add ("isbanned", "False");
192                         defaultCaps.Add ("ismobiledevice", "False");
193                         defaultCaps.Add ("issyndicationreader", "False");
194                         defaultCaps.Add ("javaapplets", "False");
195                         defaultCaps.Add ("javascript", "False");
196                         defaultCaps.Add ("majorver", "0");
197                         defaultCaps.Add ("minorver", "0");
198                         defaultCaps.Add ("msdomversion", "0.0");
199                         defaultCaps.Add ("netclr", "False");
200                         defaultCaps.Add ("platform", "unknown");
201                         defaultCaps.Add ("stripper", "False");
202                         defaultCaps.Add ("supportscss", "False");
203                         defaultCaps.Add ("tables", "False");
204                         defaultCaps.Add ("vbscript", "False");
205                         defaultCaps.Add ("version", "0");
206                         defaultCaps.Add ("w3cdomversion", "0.0");
207                         defaultCaps.Add ("wap", "False");
208                         defaultCaps.Add ("win16", "False");
209                         defaultCaps.Add ("win32", "False");
210                         defaultCaps.Add ("win64", "False");
211                         defaultCaps.Add ("adapters", new Hashtable ());
212                         defaultCaps.Add ("cancombineformsindeck", "False");
213                         defaultCaps.Add ("caninitiatevoicecall", "False");
214                         defaultCaps.Add ("canrenderafterinputorselectelement", "False");
215                         defaultCaps.Add ("canrenderemptyselects", "False");
216                         defaultCaps.Add ("canrenderinputandselectelementstogether", "False");
217                         defaultCaps.Add ("canrendermixedselects", "False");
218                         defaultCaps.Add ("canrenderoneventandprevelementstogether", "False");
219                         defaultCaps.Add ("canrenderpostbackcards", "False");
220                         defaultCaps.Add ("canrendersetvarzerowithmultiselectionlist", "False");
221                         defaultCaps.Add ("cansendmail", "False");
222                         defaultCaps.Add ("defaultsubmitbuttonlimit", "0");
223                         defaultCaps.Add ("gatewayminorversion", "0");
224                         defaultCaps.Add ("gatewaymajorversion", "0");
225                         defaultCaps.Add ("gatewayversion", "None");
226                         defaultCaps.Add ("hasbackbutton", "True");
227                         defaultCaps.Add ("hidesrightalignedmultiselectscrollbars", "False");
228                         defaultCaps.Add ("inputtype", "telephoneKeypad");
229                         defaultCaps.Add ("iscolor", "False");
230                         defaultCaps.Add ("jscriptversion", "0.0");
231                         defaultCaps.Add ("maximumhreflength", "0");
232                         defaultCaps.Add ("maximumrenderedpagesize", "2000");
233                         defaultCaps.Add ("maximumsoftkeylabellength", "5");
234                         defaultCaps.Add ("minorversionstring", "0.0");
235                         defaultCaps.Add ("mobiledevicemanufacturer", "Unknown");
236                         defaultCaps.Add ("mobiledevicemodel", "Unknown");
237                         defaultCaps.Add ("numberofsoftkeys", "0");
238                         defaultCaps.Add ("preferredimagemime", "image/gif");
239                         defaultCaps.Add ("preferredrenderingmime", "text/html");
240                         defaultCaps.Add ("preferredrenderingtype", "html32");
241                         defaultCaps.Add ("preferredrequestencoding", "");
242                         defaultCaps.Add ("preferredresponseencoding", "");
243                         defaultCaps.Add ("rendersbreakbeforewmlselectandinput", "False");
244                         defaultCaps.Add ("rendersbreaksafterhtmllists", "True");
245                         defaultCaps.Add ("rendersbreaksafterwmlanchor", "False");
246                         defaultCaps.Add ("rendersbreaksafterwmlinput", "False");
247                         defaultCaps.Add ("renderswmldoacceptsinline", "True");
248                         defaultCaps.Add ("renderswmlselectsasmenucards", "False");
249                         defaultCaps.Add ("requiredmetatagnamevalue", "");
250                         defaultCaps.Add ("requiresattributecolonsubstitution", "False");
251                         defaultCaps.Add ("requirescontenttypemetatag", "False");
252                         defaultCaps.Add ("requirescontrolstateinsession", "False");
253                         defaultCaps.Add ("requiresdbcscharacter", "False");
254                         defaultCaps.Add ("requireshtmladaptiveerrorreporting", "False");
255                         defaultCaps.Add ("requiresleadingpagebreak", "False");
256                         defaultCaps.Add ("requiresnobreakinformatting", "False");
257                         defaultCaps.Add ("requiresoutputoptimization", "False");
258                         defaultCaps.Add ("requiresphonenumbersasplaintext", "False");
259                         defaultCaps.Add ("requiresspecialviewstateencoding", "False");
260                         defaultCaps.Add ("requiresuniquefilepathsuffix", "False");
261                         defaultCaps.Add ("requiresuniquehtmlcheckboxnames", "False");
262                         defaultCaps.Add ("requiresuniquehtmlinputnames", "False");
263                         defaultCaps.Add ("requiresurlencodedpostfieldvalues", "False");
264                         defaultCaps.Add ("screenbitdepth", "1");
265                         defaultCaps.Add ("screencharactersheight", "6");
266                         defaultCaps.Add ("screencharacterswidth", "12");
267                         defaultCaps.Add ("screenpixelsheight", "72");
268                         defaultCaps.Add ("screenpixelswidth", "96");
269                         defaultCaps.Add ("supportsaccesskeyattribute", "False");
270                         defaultCaps.Add ("supportsbodycolor", "True");
271                         defaultCaps.Add ("supportsbold", "False");
272                         defaultCaps.Add ("supportscachecontrolmetatag", "True");
273                         defaultCaps.Add ("supportscallback", "False");
274                         defaultCaps.Add ("supportsdivalign", "True");
275                         defaultCaps.Add ("supportsdivnowrap", "False");
276                         defaultCaps.Add ("supportsemptystringincookievalue", "False");
277                         defaultCaps.Add ("supportsfontcolor", "True");
278                         defaultCaps.Add ("supportsfontname", "False");
279                         defaultCaps.Add ("supportsfontsize", "False");
280                         defaultCaps.Add ("supportsimagesubmit", "False");
281                         defaultCaps.Add ("supportsimodesymbols", "False");
282                         defaultCaps.Add ("supportsinputistyle", "False");
283                         defaultCaps.Add ("supportsinputmode", "False");
284                         defaultCaps.Add ("supportsitalic", "False");
285                         defaultCaps.Add ("supportsjphonemultimediaattributes", "False");
286                         defaultCaps.Add ("supportsjphonesymbols", "False");
287                         defaultCaps.Add ("supportsquerystringinformaction", "True");
288                         defaultCaps.Add ("supportsredirectwithcookie", "True");
289                         defaultCaps.Add ("supportsselectmultiple", "True");
290                         defaultCaps.Add ("supportsuncheck", "True");
291                         defaultCaps.Add ("supportsxmlhttp", "False");
292                         defaultCaps.Add ("type", "Unknown");
293                 }
294                 
295                 public static Hashtable GetCapabilities (string userAgent)
296                 {
297                         Init ();
298                         if (userAgent != null)
299                                 userAgent = userAgent.Trim ();
300
301                         if (alldata == null || userAgent == null || userAgent.Length == 0)
302                                 return defaultCaps;
303
304                         Hashtable userBrowserCaps = (Hashtable) (userAgentsCache.Contains(userAgent)? userAgentsCache [userAgent] : null);
305                         if (userBrowserCaps == null) {
306                                 foreach (BrowserData bd in alldata) {
307                                         if (bd.IsMatch (userAgent)) {
308                                                 Hashtable tbl;
309                                                 tbl = new Hashtable (defaultCaps, StringComparer.OrdinalIgnoreCase);
310                                                 userBrowserCaps = bd.GetProperties (tbl);
311                                                 break;
312                                         }
313                                 }
314
315                                 if (userBrowserCaps == null)
316                                         userBrowserCaps = defaultCaps;
317
318                                 lock (lockobj) {
319                                         if (userAgentsCache.Count >= userAgentsCacheSize)
320                                                 userAgentsCache.Clear ();
321                                 }
322                                 userAgentsCache [userAgent] = userBrowserCaps;
323                         }
324                         return userBrowserCaps;
325                 }
326
327                 static void Init ()
328                 {
329                         if (loaded)
330                                 return;
331
332                         lock (lockobj) {
333                                 if (loaded)
334                                         return;
335                                 string dir = HttpRuntime.MachineConfigurationDirectory;
336                                 string filepath = Path.Combine (dir, "browscap.ini");
337                                 if (!File.Exists (filepath)) {
338                                         // try removing the trailing version directory
339                                         dir = Path.GetDirectoryName (dir);
340                                         filepath = Path.Combine (dir, "browscap.ini");
341                                 }
342                                 try {
343                                         LoadFile (filepath);
344                                 } catch (Exception) {}
345
346                                 loaded = true;
347                         }
348                 }
349
350
351                 static void LoadFile (string filename)
352                 {
353                         if (!File.Exists (filename))
354                                 return;
355
356                         TextReader input = new StreamReader (File.OpenRead (filename));
357                         using (input) {
358                         string str;
359                         Hashtable allhash = new Hashtable (StringComparer.OrdinalIgnoreCase);
360                         int aux = 0;
361                         ArrayList browserData = new ArrayList ();
362                         while ((str = input.ReadLine ()) != null) {
363                                 if (str.Length == 0 || str [0] == ';')
364                                         continue;
365
366                                 string userAgent = str.Substring (1, str.Length - 2);
367                                 BrowserData data = new BrowserData (userAgent);
368                                 ReadCapabilities (input, data);
369
370                                 /* Ignore default browser and file version information */
371                                 if (userAgent == "*" || userAgent == "GJK_Browscap_Version")
372                                         continue;
373
374                                 string key = data.GetBrowser ();
375                                 if (key == null || allhash.ContainsKey (key)) {
376                                         allhash.Add (aux++, data);
377                                         browserData.Add (data);
378                                 } else {
379                                         allhash.Add (key, data);
380                                         browserData.Add (data);
381                                 }
382                         }                       
383
384                         alldata = browserData;
385                         foreach (BrowserData data in alldata) {
386                                 string pname = data.GetParentName ();
387                                 if (pname == null)
388                                         continue;
389
390                                 data.Parent = (BrowserData) allhash [pname];
391                         }
392                         }
393                 }
394
395                 static char [] eq = new char []{'='};
396                 static void ReadCapabilities (TextReader input, BrowserData data)
397                 {
398                         string str, key;
399                         string [] keyvalue;
400                         
401                         while ((str = input.ReadLine ()) != null && str.Length != 0) {
402                                 keyvalue = str.Split (eq, 2);
403                                 key = keyvalue [0].ToLower (Helpers.InvariantCulture).Trim ();
404                                 if (key.Length == 0)
405                                         continue;
406                                 data.Add (key, keyvalue [1]);
407                         }
408                 }
409         }
410 }