1 //------------------------------------------------------------------------------
2 // <copyright file="HttpCapabilitiesSectionHandler.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 namespace System.Web.Configuration {
9 using System.Collections;
10 using System.Configuration;
11 using System.Diagnostics.CodeAnalysis;
13 using System.Security;
14 using System.Security.Permissions;
15 using System.Text.RegularExpressions;
16 using System.Web.Configuration;
17 using System.Web.Util;
19 using Pair = System.Web.UI.Pair;
22 // ConfigureCapabilities is used to configure the CapabilitiesEvaluator object
24 public class HttpCapabilitiesSectionHandler : IConfigurationSectionHandler {
26 private const int _defaultUserAgentCacheKeyLength = 64;
28 public HttpCapabilitiesSectionHandler() {
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;
39 internal ParseState() {}
43 // As required by IConfigurationSectionHandler
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)) {
51 ParseState parseState = new ParseState();
52 parseState.SectionName = section.Name;
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);
57 int userAgentCacheKeyLength = 0;
58 // Get the useragent string cachekey length
60 userAgentCacheKeyLength = ((HttpCapabilitiesDefaultProvider)parent).UserAgentCacheKeyLength;
62 HandlerBase.GetAndRemovePositiveIntegerAttribute(section, "userAgentCacheKeyLength", ref userAgentCacheKeyLength);
64 if (userAgentCacheKeyLength == 0) {
65 userAgentCacheKeyLength = _defaultUserAgentCacheKeyLength;
67 parseState.Evaluator.UserAgentCacheKeyLength = userAgentCacheKeyLength;
69 string browserCapabilitiesProviderType = null;
71 browserCapabilitiesProviderType = ((HttpCapabilitiesDefaultProvider)parent).BrowserCapabilitiesProviderType;
73 HandlerBase.GetAndRemoveNonEmptyStringAttribute(section, "provider", ref browserCapabilitiesProviderType);
74 parseState.Evaluator.BrowserCapabilitiesProviderType = browserCapabilitiesProviderType;
76 // check for random attributes
77 HandlerBase.CheckForUnrecognizedAttributes(section);
80 // iterate through XML section in order and apply the directives
84 sublist = RuleListFromElement(parseState, section, true);
86 if (sublist.Count > 0) {
87 parseState.RuleList.Add(new CapabilitiesSection(CapabilitiesRule.Filter, null, null, sublist));
90 if (parseState.FileList.Count > 0) {
91 parseState.IsExternalFile = true;
92 ResolveFiles(parseState, configurationContext);
97 parseState.Evaluator.AddRuleList(parseState.RuleList);
99 return parseState.Evaluator;
103 // Create a rule from an element
105 static CapabilitiesRule RuleFromElement(ParseState parseState, XmlNode element) {
108 CapabilitiesPattern pat;
112 if (element.Name == "filter") {
113 ruletype = CapabilitiesRule.Filter;
115 else if (element.Name=="case") {
116 ruletype = CapabilitiesRule.Case;
118 else if (element.Name == "use") {
119 HandlerBase.CheckForNonCommentChildNodes(element);
121 string var = HandlerBase.RemoveRequiredAttribute(element, "var");
122 string strAs = HandlerBase.RemoveAttribute(element, "as");
123 HandlerBase.CheckForUnrecognizedAttributes(element);
126 strAs = String.Empty;
128 parseState.Evaluator.AddDependency(var);
130 return new CapabilitiesUse(var, strAs);
133 throw new ConfigurationErrorsException(
134 SR.GetString(SR.Unknown_tag_in_caps_config, element.Name),
139 String matchpat = HandlerBase.RemoveAttribute(element, "match");
140 String testpat = HandlerBase.RemoveAttribute(element, "with");
141 HandlerBase.CheckForUnrecognizedAttributes(element);
143 if (matchpat == null) {
145 throw new ConfigurationErrorsException(SR.GetString(SR.Cannot_specify_test_without_match), element);
151 regex = new DelayedRegex(matchpat);
153 catch (Exception e) {
154 throw new ConfigurationErrorsException(e.Message, e, element);
158 pat = CapabilitiesPattern.Default;
160 pat = new CapabilitiesPattern(testpat);
164 ArrayList subrules = RuleListFromElement(parseState, element, false);
166 return new CapabilitiesSection(ruletype, regex, pat, subrules);
170 // Create a rulelist from an element's children
172 static ArrayList RuleListFromElement(ParseState parseState, XmlNode node, bool top) {
173 ArrayList result = new ArrayList();
175 foreach (XmlNode child in node.ChildNodes) {
176 switch (child.NodeType) {
177 case XmlNodeType.Text:
178 case XmlNodeType.CDATA:
180 AppendLines(result, child.Value, node);
183 case XmlNodeType.Element:
184 switch (child.Name) {
187 ProcessResult(parseState.Evaluator, child);
190 throw new ConfigurationErrorsException(
191 SR.GetString(SR.Result_must_be_at_the_top_browser_section),
197 if (parseState.IsExternalFile) {
198 throw new ConfigurationErrorsException(
199 SR.GetString(SR.File_element_only_valid_in_config),
202 ProcessFile(parseState.FileList, child);
206 result.Add(RuleFromElement(parseState, child));
212 case XmlNodeType.Comment:
213 case XmlNodeType.Whitespace:
217 HandlerBase.ThrowUnrecognizedElement(child);
227 // Handle <file src=""/> elements
229 static void ProcessFile(ArrayList fileList, XmlNode node) {
231 string filename = null;
232 XmlNode attr = HandlerBase.GetAndRemoveRequiredStringAttribute(node, "src", ref filename);
234 HandlerBase.CheckForUnrecognizedAttributes(node);
235 HandlerBase.CheckForNonCommentChildNodes(node);
237 fileList.Add(new Pair(filename, attr));
242 // Handle the <result> tag
244 static void ProcessResult(HttpCapabilitiesDefaultProvider capabilitiesEvaluator, XmlNode node) {
247 HandlerBase.GetAndRemoveBooleanAttribute(node, "inherit", ref inherit);
248 if (inherit == false) {
249 capabilitiesEvaluator.ClearParent();
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;
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);
270 HandlerBase.CheckForUnrecognizedAttributes(node);
271 HandlerBase.CheckForNonCommentChildNodes(node);
277 // ResolveFiles - parse files referenced with <file src="" />
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) {
283 // 1) get the directory of the configuration file currently being parsed
286 HttpConfigurationContext httpConfigurationContext = (HttpConfigurationContext) configurationContext;
287 string configurationDirectory = null;
288 bool useAssert = false;
291 // Only assert to read cap files when parsing machine.config
292 // (allow device updates to work in restricted trust levels).
294 // Machine.config can be securely identified by the context being
295 // an HttpConfigurationContext with null path.
298 if (httpConfigurationContext.VirtualPath == null) {
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;
306 Pair pair0 = (Pair)parseState.FileList[0];
307 XmlNode srcAttribute = (XmlNode)pair0.Second;
308 configurationDirectory = Path.GetDirectoryName(ConfigurationErrorsException.GetFilename(srcAttribute));
312 CodeAccessPermission.RevertAssert();
317 // 2) iterate through list of referenced files, builing rule lists for each
319 foreach (Pair pair in parseState.FileList) {
320 string srcFilename = (string)pair.First;
321 string fullFilename = Path.Combine(configurationDirectory, srcFilename);
326 InternalSecurityPermissions.FileReadAccess(fullFilename).Assert();
329 Exception fcmException = null;
332 HttpConfigurationSystem.AddFileDependency(fullFilename);
334 catch (Exception e) {
338 ConfigXmlDocument configDoc = new ConfigXmlDocument();
341 configDoc.Load(fullFilename);
342 section = configDoc.DocumentElement;
344 catch (Exception e) {
345 throw new ConfigurationErrorsException(SR.GetString(SR.Error_loading_XML_file, fullFilename, e.Message),
346 e, (XmlNode)pair.Second);
349 if (fcmException != null) {
355 // Cannot apply next FileReadAccess PermissionSet unless
356 // current set is explicitly reverted. Also minimizes
357 // granted permissions.
358 CodeAccessPermission.RevertAssert();
362 if (section.Name != parseState.SectionName) {
363 throw new ConfigurationErrorsException(SR.GetString(SR.Capability_file_root_element, parseState.SectionName),
367 HandlerBase.CheckForUnrecognizedAttributes(section);
369 ArrayList sublist = RuleListFromElement(parseState, section, true);
371 if (sublist.Count > 0) {
372 parseState.RuleList.Add(new CapabilitiesSection(CapabilitiesRule.Filter, null, null, sublist));
378 // Regex dealing with a=b lines
380 static Regex lineRegex = new Regex
383 "(?<var>\\w+)" + // variable name
384 "\\s*=\\s*" + // equals sign
386 "\"(?<pat>[^\"\r\n\\\\]*" + // quoted pattern
387 "(?:\\\\.[^\"\r\n\\\\]*)*)\"" +
388 "|(?!\")(?<pat>\\S+)" + // unquoted pattern
390 "\\s*" // trailing whitespace
393 static Regex wsRegex = new Regex("\\G\\s*");
394 static Regex errRegex = new Regex("\\G\\S {0,8}");
396 static void AppendLines(ArrayList setlist, String text, XmlNode node) {
397 int lineNumber = ConfigurationErrorsException.GetLineNumber(node);
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;
410 if (textpos == text.Length)
413 if ((match = lineRegex.Match(text, textpos)).Success) {
414 setlist.Add(new CapabilitiesAssignment(match.Groups["var"].Value,
415 new CapabilitiesPattern(match.Groups["pat"].Value)));
417 lineNumber += System.Web.UI.Util.LineCount(text, textpos, match.Index + match.Length);
418 textpos = match.Index + match.Length;
421 match = errRegex.Match(text, textpos);
423 throw new ConfigurationErrorsException(
424 SR.GetString(SR.Problem_reading_caps_config, match.ToString()),
425 ConfigurationErrorsException.GetFilename(node),