Merge pull request #3389 from lambdageek/bug-43099
[mono.git] / mcs / class / referencesource / System.Web / Configuration / HttpCapabilitiesSectionHandler.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="HttpCapabilitiesSectionHandler.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 namespace System.Web.Configuration {
8
9     using System.Collections;
10     using System.Configuration;
11     using System.Diagnostics.CodeAnalysis;
12     using System.IO;
13     using System.Security;
14     using System.Security.Permissions;
15     using System.Text.RegularExpressions;
16     using System.Web.Configuration;
17     using System.Web.Util;
18     using System.Xml;
19     using Pair = System.Web.UI.Pair;
20
21     //
22     // ConfigureCapabilities is used to configure the CapabilitiesEvaluator object
23     //
24     public class HttpCapabilitiesSectionHandler : IConfigurationSectionHandler {
25
26         private const int _defaultUserAgentCacheKeyLength = 64;
27
28         public HttpCapabilitiesSectionHandler() {
29         }
30         
31
32         class ParseState {
33             internal string SectionName;
34             internal HttpCapabilitiesDefaultProvider Evaluator;
35             internal ArrayList RuleList = new ArrayList();
36             internal ArrayList FileList = new ArrayList();
37             internal bool IsExternalFile = false;
38             
39             internal ParseState() {}
40         }
41
42         //
43         // As required by IConfigurationSectionHandler
44         //
45         public object Create(object parent, object configurationContext, XmlNode section) {
46             // if called through client config don't even load HttpRuntime
47             if (!HandlerBase.IsServerConfiguration(configurationContext)) {
48                 return null;
49             }
50
51             ParseState parseState = new ParseState();
52             parseState.SectionName = section.Name;
53
54             // the rule is going to be the previous rule followed by a list containing the new rules
55             parseState.Evaluator = new HttpCapabilitiesDefaultProvider((HttpCapabilitiesDefaultProvider)parent);
56
57             int userAgentCacheKeyLength = 0;
58             // Get the useragent string cachekey length
59             if (parent != null) {
60                 userAgentCacheKeyLength = ((HttpCapabilitiesDefaultProvider)parent).UserAgentCacheKeyLength;
61             }
62             HandlerBase.GetAndRemovePositiveIntegerAttribute(section, "userAgentCacheKeyLength", ref userAgentCacheKeyLength);
63
64             if (userAgentCacheKeyLength == 0) {
65                 userAgentCacheKeyLength = _defaultUserAgentCacheKeyLength;
66             }
67             parseState.Evaluator.UserAgentCacheKeyLength = userAgentCacheKeyLength;
68
69             string browserCapabilitiesProviderType = null;
70             if (parent != null) {
71                 browserCapabilitiesProviderType = ((HttpCapabilitiesDefaultProvider)parent).BrowserCapabilitiesProviderType;
72             }
73             HandlerBase.GetAndRemoveNonEmptyStringAttribute(section, "provider", ref browserCapabilitiesProviderType);
74             parseState.Evaluator.BrowserCapabilitiesProviderType = browserCapabilitiesProviderType;
75
76             // check for random attributes
77             HandlerBase.CheckForUnrecognizedAttributes(section);
78
79
80             // iterate through XML section in order and apply the directives
81
82             ArrayList sublist;
83
84             sublist = RuleListFromElement(parseState, section, true);
85
86             if (sublist.Count > 0) {
87                 parseState.RuleList.Add(new CapabilitiesSection(CapabilitiesRule.Filter, null, null, sublist));
88             }
89
90             if (parseState.FileList.Count > 0) {
91                 parseState.IsExternalFile = true;
92                 ResolveFiles(parseState, configurationContext);
93             }
94             
95             // Add the new rules
96
97             parseState.Evaluator.AddRuleList(parseState.RuleList);
98
99             return parseState.Evaluator;
100         }
101
102         //
103         // Create a rule from an element
104         //
105         static CapabilitiesRule RuleFromElement(ParseState parseState, XmlNode element) {
106             int ruletype;
107             DelayedRegex regex;
108             CapabilitiesPattern pat;
109
110             // grab tag name
111
112             if (element.Name == "filter") {
113                 ruletype = CapabilitiesRule.Filter;
114             }
115             else if (element.Name=="case") {
116                 ruletype = CapabilitiesRule.Case;
117             }
118             else if (element.Name == "use") {
119                 HandlerBase.CheckForNonCommentChildNodes(element);
120
121                 string var = HandlerBase.RemoveRequiredAttribute(element, "var");
122                 string strAs = HandlerBase.RemoveAttribute(element, "as");
123                 HandlerBase.CheckForUnrecognizedAttributes(element);
124
125                 if (strAs == null)
126                     strAs = String.Empty;
127
128                 parseState.Evaluator.AddDependency(var);
129
130                 return new CapabilitiesUse(var, strAs);
131             }
132             else {
133                 throw new ConfigurationErrorsException(
134                                                 SR.GetString(SR.Unknown_tag_in_caps_config, element.Name), 
135                                                 element);
136             }
137
138             // grab attributes
139             String matchpat = HandlerBase.RemoveAttribute(element, "match");
140             String testpat = HandlerBase.RemoveAttribute(element, "with");
141             HandlerBase.CheckForUnrecognizedAttributes(element);
142
143             if (matchpat == null) {
144                 if (testpat != null)
145                     throw new ConfigurationErrorsException(SR.GetString(SR.Cannot_specify_test_without_match), element);
146                 regex = null;
147                 pat = null;
148             }
149             else {
150                 try {
151                     regex = new DelayedRegex(matchpat);
152                 }
153                 catch (Exception e) {
154                     throw new ConfigurationErrorsException(e.Message, e, element);
155                 }
156
157                 if (testpat == null)
158                     pat = CapabilitiesPattern.Default;
159                 else
160                     pat = new CapabilitiesPattern(testpat);
161             }
162
163             // grab contents
164             ArrayList subrules = RuleListFromElement(parseState, element, false);
165
166             return new CapabilitiesSection(ruletype, regex, pat, subrules);
167         }
168
169         //
170         // Create a rulelist from an element's children
171         //
172         static ArrayList RuleListFromElement(ParseState parseState, XmlNode node, bool top) {
173             ArrayList result = new ArrayList();
174
175             foreach (XmlNode child in node.ChildNodes) {
176                 switch (child.NodeType) {
177                     case XmlNodeType.Text:
178                     case XmlNodeType.CDATA:
179                         top = false;
180                         AppendLines(result, child.Value, node);
181                         break;
182
183                     case XmlNodeType.Element:
184                         switch (child.Name) {
185                             case "result":
186                                 if (top) {
187                                     ProcessResult(parseState.Evaluator, child);
188                                 }
189                                 else {
190                                     throw new ConfigurationErrorsException(
191                                                     SR.GetString(SR.Result_must_be_at_the_top_browser_section), 
192                                                     child);
193                                 }
194                                 break;
195                                 
196                             case "file": 
197                                 if (parseState.IsExternalFile) {
198                                     throw new ConfigurationErrorsException(
199                                                     SR.GetString(SR.File_element_only_valid_in_config),
200                                                     child);
201                                 }
202                                 ProcessFile(parseState.FileList, child);
203                                 break;
204
205                             default: 
206                                 result.Add(RuleFromElement(parseState, child));
207                                 break;
208                         }
209                         top = false;
210                         break;
211
212                     case XmlNodeType.Comment:
213                     case XmlNodeType.Whitespace:
214                         break;
215
216                     default:
217                         HandlerBase.ThrowUnrecognizedElement(child);
218                         break;
219                 }
220             }
221
222             return result;
223         }
224
225
226         //
227         // Handle <file src=""/> elements
228         //
229         static void ProcessFile(ArrayList fileList, XmlNode node) {
230             
231             string filename = null;
232             XmlNode attr = HandlerBase.GetAndRemoveRequiredStringAttribute(node, "src", ref filename);
233
234             HandlerBase.CheckForUnrecognizedAttributes(node);
235             HandlerBase.CheckForNonCommentChildNodes(node);
236
237             fileList.Add(new Pair(filename, attr));
238         }
239
240
241         //
242         // Handle the <result> tag
243         //
244         static void ProcessResult(HttpCapabilitiesDefaultProvider capabilitiesEvaluator, XmlNode node) {
245
246             bool inherit = true;
247             HandlerBase.GetAndRemoveBooleanAttribute(node, "inherit", ref inherit);
248             if (inherit == false) {
249                 capabilitiesEvaluator.ClearParent();
250             }
251
252             Type resultType = null;
253             XmlNode attribute = HandlerBase.GetAndRemoveTypeAttribute(node, "type", ref resultType);
254             if (attribute != null) {
255                 if (resultType.Equals(capabilitiesEvaluator._resultType) == false) {
256                     // be sure the new type is assignable to the parent type
257                     HandlerBase.CheckAssignableType(attribute, capabilitiesEvaluator._resultType, resultType);
258                     capabilitiesEvaluator._resultType = resultType;
259                 }
260             }
261
262             int cacheTime = 0;
263             attribute = HandlerBase.GetAndRemovePositiveIntegerAttribute(node, "cacheTime", ref cacheTime);
264             //NOTE: we continue to parse the cacheTime for backwards compat
265             // it has never been used.  Customer scenarios don't require this support.
266             if (attribute != null) {
267                 capabilitiesEvaluator.CacheTime = TimeSpan.FromSeconds(cacheTime);
268             }
269
270             HandlerBase.CheckForUnrecognizedAttributes(node);
271             HandlerBase.CheckForNonCommentChildNodes(node);
272
273         }
274
275
276         // 
277         // ResolveFiles - parse files referenced with <file src="" />
278         //
279         [SuppressMessage("Microsoft.Security.Xml", "CA3056:UseXmlReaderForLoad", Justification = "Developer-controlled .xml files in application directory are implicitly trusted by ASP.Net.")]
280         static void ResolveFiles(ParseState parseState, object configurationContext) {
281
282             //
283             // 1) get the directory of the configuration file currently being parsed
284             //
285
286             HttpConfigurationContext httpConfigurationContext = (HttpConfigurationContext) configurationContext;
287             string configurationDirectory = null;
288             bool useAssert = false;
289
290             //
291             // Only assert to read cap files when parsing machine.config 
292             // (allow device updates to work in restricted trust levels).
293             //
294             // Machine.config can be securely identified by the context being 
295             // an HttpConfigurationContext with null path.
296             //
297             try {
298                 if (httpConfigurationContext.VirtualPath == null) {
299                     useAssert = true;
300                     // we need to assert here to get the file path from ConfigurationException
301                     FileIOPermission fiop = new FileIOPermission(PermissionState.None);
302                     fiop.AllFiles = FileIOPermissionAccess.PathDiscovery;
303                     fiop.Assert();
304                 }
305                 
306                 Pair pair0 = (Pair)parseState.FileList[0];
307                 XmlNode srcAttribute = (XmlNode)pair0.Second;
308                 configurationDirectory = Path.GetDirectoryName(ConfigurationErrorsException.GetFilename(srcAttribute));
309             }
310             finally {
311                 if (useAssert) {
312                     CodeAccessPermission.RevertAssert();
313                 }
314             }
315
316             //
317             // 2) iterate through list of referenced files, builing rule lists for each
318             //
319             foreach (Pair pair in parseState.FileList) {
320                 string srcFilename = (string)pair.First;
321                 string fullFilename = Path.Combine(configurationDirectory, srcFilename);
322
323                 XmlNode section;
324                 try {
325                     if (useAssert) {
326                         InternalSecurityPermissions.FileReadAccess(fullFilename).Assert();
327                     }
328                     
329                     Exception fcmException = null;
330                     
331                     try {
332                         HttpConfigurationSystem.AddFileDependency(fullFilename);
333                     }
334                     catch (Exception e) {
335                         fcmException = e;
336                     }
337
338                     ConfigXmlDocument configDoc = new ConfigXmlDocument();
339                     
340                     try {
341                         configDoc.Load(fullFilename);
342                         section = configDoc.DocumentElement;
343                     }
344                     catch (Exception e) {
345                         throw new ConfigurationErrorsException(SR.GetString(SR.Error_loading_XML_file, fullFilename, e.Message), 
346                                         e, (XmlNode)pair.Second);
347                     }
348
349                     if (fcmException != null) {
350                         throw fcmException;
351                     }
352                 }
353                 finally {
354                     if (useAssert) {
355                         // Cannot apply next FileReadAccess PermissionSet unless 
356                         // current set is explicitly reverted.  Also minimizes
357                         // granted permissions.
358                         CodeAccessPermission.RevertAssert();
359                     }
360                 }
361                 
362                 if (section.Name != parseState.SectionName) {
363                     throw new ConfigurationErrorsException(SR.GetString(SR.Capability_file_root_element, parseState.SectionName), 
364                                     section);
365                 }
366                     
367                 HandlerBase.CheckForUnrecognizedAttributes(section);
368
369                 ArrayList sublist = RuleListFromElement(parseState, section, true);
370
371                 if (sublist.Count > 0) {
372                     parseState.RuleList.Add(new CapabilitiesSection(CapabilitiesRule.Filter, null, null, sublist));
373                 }
374             }
375         }
376
377         //
378         // Regex dealing with a=b lines
379         //
380         static Regex lineRegex = new Regex
381                                           (
382                                           "\\G" +                                 // anchored
383                                           "(?<var>\\w+)" +                        // variable name
384                                           "\\s*=\\s*" +                           // equals sign
385                                           "(?:" +
386                                           "\"(?<pat>[^\"\r\n\\\\]*" +         // quoted pattern
387                                           "(?:\\\\.[^\"\r\n\\\\]*)*)\"" +
388                                           "|(?!\")(?<pat>\\S+)" +             // unquoted pattern
389                                           ")" +
390                                           "\\s*"                                  // trailing whitespace
391                                           );
392
393         static Regex wsRegex = new Regex("\\G\\s*");
394         static Regex errRegex = new Regex("\\G\\S {0,8}");
395
396         static void AppendLines(ArrayList setlist, String text, XmlNode node) {
397             int lineNumber = ConfigurationErrorsException.GetLineNumber(node);
398             int textpos;
399
400             textpos = 0;
401
402             for (;;) {
403                 Match match;
404
405                 if ((match = wsRegex.Match(text, textpos)).Success) {
406                     lineNumber += System.Web.UI.Util.LineCount(text, textpos, match.Index + match.Length);
407                     textpos = match.Index + match.Length;
408                 }
409
410                 if (textpos == text.Length)
411                     break;
412
413                 if ((match = lineRegex.Match(text, textpos)).Success) {
414                     setlist.Add(new CapabilitiesAssignment(match.Groups["var"].Value,
415                                                            new CapabilitiesPattern(match.Groups["pat"].Value)));
416
417                     lineNumber += System.Web.UI.Util.LineCount(text, textpos, match.Index + match.Length);
418                     textpos = match.Index + match.Length;
419                 }
420                 else {
421                     match = errRegex.Match(text, textpos);
422
423                     throw new ConfigurationErrorsException(
424                                     SR.GetString(SR.Problem_reading_caps_config, match.ToString()), 
425                                     ConfigurationErrorsException.GetFilename(node),
426                                     lineNumber);
427                 }
428             }
429         }
430
431     }
432 }