//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.UI { using System; using System.IO; using System.Web.Configuration; using System.Collections; using System.Collections.Specialized; using System.Threading; using System.Globalization; using System.Web.Hosting; using System.Web.Caching; using System.Web.Util; using System.Web.Compilation; using HttpException = System.Web.HttpException; using Debug=System.Web.Util.Debug; using System.Text.RegularExpressions; internal abstract class DependencyParser : BaseParser { private VirtualPath _virtualPath; private StringSet _virtualPathDependencies; // Used to detect circular references private StringSet _circularReferenceChecker = new CaseInsensitiveStringSet(); // The config section private PagesSection _pagesConfig; protected PagesSection PagesConfig { get { return _pagesConfig; } } internal void Init(VirtualPath virtualPath) { CurrentVirtualPath = virtualPath; _virtualPath = virtualPath; _pagesConfig = MTConfigUtil.GetPagesConfig(virtualPath); } internal ICollection GetVirtualPathDependencies() { // Always set the culture to Invariant when parsing (ASURT 99071) Thread currentThread = Thread.CurrentThread; CultureInfo prevCulture = currentThread.CurrentCulture; HttpRuntime.SetCurrentThreadCultureWithAssert(CultureInfo.InvariantCulture); try { try { PrepareParse(); ParseFile(); } finally { // Restore the previous culture HttpRuntime.SetCurrentThreadCultureWithAssert(prevCulture); } } catch { throw; } // Prevent Exception Filter Security Issue (ASURT 122835) return _virtualPathDependencies; } protected void AddDependency(VirtualPath virtualPath) { virtualPath = ResolveVirtualPath(virtualPath); Debug.Trace("Template", "Parsed dependency: " + _virtualPath + " depends on " + virtualPath); if (_virtualPathDependencies == null) _virtualPathDependencies = new CaseInsensitiveStringSet(); _virtualPathDependencies.Add(virtualPath.VirtualPathString); } internal abstract string DefaultDirectiveName { get; } protected virtual void PrepareParse() {} private void ParseFile() { ParseFile(null /*physicalPath*/, _virtualPath); } private void ParseFile(string physicalPath, VirtualPath virtualPath) { // Determine the file used for the circular references checker. Normally, // we use the virtualPath, but we use the physical path if it specified, // as is the case for string fileToReferenceCheck = physicalPath != null ? physicalPath : virtualPath.VirtualPathString; // Check for circular references of include files if (_circularReferenceChecker.Contains(fileToReferenceCheck)) { throw new HttpException( SR.GetString(SR.Circular_include)); } // Add the current file to the circular references checker. _circularReferenceChecker.Add(fileToReferenceCheck); try { // Open a TextReader either from the physical or virtual path TextReader reader; if (physicalPath != null) { using (reader = Util.ReaderFromFile(physicalPath, virtualPath)) { ParseReader(reader); } } else { using (Stream stream = virtualPath.OpenFile()) { reader = Util.ReaderFromStream(stream, virtualPath); ParseReader(reader); } } } finally { // Remove the current file from the circular references checker _circularReferenceChecker.Remove(fileToReferenceCheck); } } private void ParseReader(TextReader input) { ParseString(input.ReadToEnd()); } private void ParseString(string text) { int textPos = 0; for (;;) { Match match; // 1: scan for text up to the next tag. if ((match = textRegex.Match(text, textPos)).Success) { textPos = match.Index + match.Length; } // we might be done now if (textPos == text.Length) break; // 2: handle constructs that start with < // Check to see if it's a directive (i.e. <%@ %> block) if ((match = directiveRegex.Match(text, textPos)).Success) { IDictionary directive = CollectionsUtil.CreateCaseInsensitiveSortedList(); string directiveName = ProcessAttributes(match, directive); ProcessDirective(directiveName, directive); textPos = match.Index + match.Length; } else if ((match = includeRegex.Match(text, textPos)).Success) { ProcessServerInclude(match); textPos = match.Index + match.Length; } else if ((match = commentRegex.Match(text, textPos)).Success) { // Just skip it textPos = match.Index + match.Length; } else { int newPos = text.IndexOf("<%@", textPos, StringComparison.Ordinal); // 2nd condition is used to catch invalid directives, e.g. <%@ attr="value_without_end_quote > if (newPos == -1 || newPos == textPos) { return; } textPos = newPos; } // we might be done now if (textPos == text.Length) return; } } /* * Process a server side include. e.g. */ private void ProcessServerInclude(Match match) { string pathType = match.Groups["pathtype"].Value; string filename = match.Groups["filename"].Value; if (filename.Length == 0) return; VirtualPath newVirtualPath; string newPhysicalPath = null; if (StringUtil.EqualsIgnoreCase(pathType, "file")) { if (UrlPath.IsAbsolutePhysicalPath(filename)) { // If it's an absolute physical path, use it as is newPhysicalPath = filename; // Reuse the current virtual path newVirtualPath = CurrentVirtualPath; } else { // If it's relative, just treat it as virtual newVirtualPath = ResolveVirtualPath(VirtualPath.Create(filename)); } } else if (StringUtil.EqualsIgnoreCase(pathType, "virtual")) { newVirtualPath = ResolveVirtualPath(VirtualPath.Create(filename)); } else { // Unknown #include type: ignore it return; } VirtualPath prevVirtualPath = _virtualPath; try { _virtualPath = newVirtualPath; // Parse the included file recursively ParseFile(newPhysicalPath, newVirtualPath); } finally { // Restore the paths _virtualPath = prevVirtualPath; } } /* * Process a <%@ %> block */ internal virtual void ProcessDirective(string directiveName, IDictionary directive) { // Get all the directives into a bag // Check for the main directive (e.g. "page" for an aspx) if (directiveName == null || StringUtil.EqualsIgnoreCase(directiveName, DefaultDirectiveName) ) { ProcessMainDirective(directive); } else if (StringUtil.EqualsIgnoreCase(directiveName, "register")) { VirtualPath src = Util.GetAndRemoveVirtualPathAttribute(directive, "src"); if (src != null) { AddDependency(src); } } else if (StringUtil.EqualsIgnoreCase(directiveName, "reference")) { VirtualPath virtualPath = Util.GetAndRemoveVirtualPathAttribute(directive, "virtualpath"); if (virtualPath != null) AddDependency(virtualPath); VirtualPath page = Util.GetAndRemoveVirtualPathAttribute(directive, "page"); if (page != null) AddDependency(page); VirtualPath control = Util.GetAndRemoveVirtualPathAttribute(directive, "control"); if (control != null) AddDependency(control); } else if (StringUtil.EqualsIgnoreCase(directiveName, "assembly")) { VirtualPath src = Util.GetAndRemoveVirtualPathAttribute(directive, "src"); if (src != null) AddDependency(src); } } private void ProcessMainDirective(IDictionary mainDirective) { // Go through all the attributes on the directive foreach (DictionaryEntry entry in mainDirective) { string attribName = ((string)entry.Key).ToLower(CultureInfo.InvariantCulture); // Parse out the device name, if any string name; string deviceName = Util.ParsePropertyDeviceFilter(attribName, out name); // Process the attribute ProcessMainDirectiveAttribute(deviceName, name, (string) entry.Value); } } internal virtual void ProcessMainDirectiveAttribute(string deviceName, string name, string value) { // A "src" attribute is equivalent to an imported source file if (name == "src") { string src = Util.GetNonEmptyAttribute(name, value); AddDependency(VirtualPath.Create(src)); } } /* * Adds attributes and their values to the attribs */ private string ProcessAttributes(Match match, IDictionary attribs) { string ret = null; CaptureCollection attrnames = match.Groups["attrname"].Captures; CaptureCollection attrvalues = match.Groups["attrval"].Captures; CaptureCollection equalsign = match.Groups["equal"].Captures; for (int i = 0; i < attrnames.Count; i++) { string attribName = attrnames[i].ToString(); string attribValue = attrvalues[i].ToString(); bool fHasEqual = (equalsign[i].ToString().Length > 0); if (attribName != null && !fHasEqual && ret == null) { ret = attribName; continue; } try { if (attribs != null) attribs.Add(attribName, attribValue); } catch (ArgumentException) {} } return ret; } } internal abstract class TemplateControlDependencyParser : DependencyParser { internal override void ProcessMainDirectiveAttribute(string deviceName, string name, string value) { switch (name) { case "masterpagefile": value = value.Trim(); if (value.Length > 0) { // Add a dependency on the master, whether it has a device filter or not AddDependency(VirtualPath.Create(value)); } break; default: // We didn't handle the attribute. Try the base class base.ProcessMainDirectiveAttribute(deviceName, name, value); break; } } } internal class PageDependencyParser : TemplateControlDependencyParser { internal override string DefaultDirectiveName { get { return PageParser.defaultDirectiveName; } } protected override void PrepareParse() { if (PagesConfig != null) { if (PagesConfig.MasterPageFileInternal != null && PagesConfig.MasterPageFileInternal.Length != 0) AddDependency(VirtualPath.Create(PagesConfig.MasterPageFileInternal)); } } internal override void ProcessDirective(string directiveName, IDictionary directive) { base.ProcessDirective(directiveName, directive); if (StringUtil.EqualsIgnoreCase(directiveName, "previousPageType") || StringUtil.EqualsIgnoreCase(directiveName, "masterType")) { VirtualPath virtualPath = Util.GetAndRemoveVirtualPathAttribute(directive, "virtualPath"); if (virtualPath != null) AddDependency(virtualPath); } } } internal class UserControlDependencyParser : TemplateControlDependencyParser { internal override string DefaultDirectiveName { get { return UserControlParser.defaultDirectiveName; } } } internal class MasterPageDependencyParser : UserControlDependencyParser { internal override string DefaultDirectiveName { get { return MasterPageParser.defaultDirectiveName; } } internal override void ProcessDirective(string directiveName, IDictionary directive) { base.ProcessDirective(directiveName, directive); if (StringUtil.EqualsIgnoreCase(directiveName, "masterType")) { VirtualPath virtualPath = Util.GetAndRemoveVirtualPathAttribute(directive, "virtualPath"); if (virtualPath != null) AddDependency(virtualPath); } } } }