X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FSystem.Web%2FSystem.Web.Compilation%2FAspGenerator.cs;h=5fc857a322d98068851191f1c7e09374d394dba4;hb=d2702cac2448b2f9f755ffec0b033924cf7af40d;hp=989e5c8dce88a371bdaff652c927c69ccc58465d;hpb=b87c7e29df7d04e3199e224c8b8e9a41292cec1b;p=mono.git diff --git a/mcs/class/System.Web/System.Web.Compilation/AspGenerator.cs b/mcs/class/System.Web/System.Web.Compilation/AspGenerator.cs index 989e5c8dce8..5fc857a322d 100644 --- a/mcs/class/System.Web/System.Web.Compilation/AspGenerator.cs +++ b/mcs/class/System.Web/System.Web.Compilation/AspGenerator.cs @@ -3,9 +3,10 @@ // // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) +// Marek Habersack // // (C) 2002,2003 Ximian, Inc (http://www.ximian.com) -// Copyright (c) 2004,2006 Novell, Inc (http://www.novell.com) +// Copyright (c) 2004-2009 Novell, Inc (http://www.novell.com) // // @@ -42,6 +43,10 @@ using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.Util; +#if NET_2_0 +using System.Collections.Generic; +#endif + namespace System.Web.Compilation { class BuilderLocation @@ -177,8 +182,53 @@ namespace System.Web.Compilation } } + enum TextBlockType + { + Verbatim, + Expression, + Tag, + Comment + } + + sealed class TextBlock + { + public string Content; + public readonly TextBlockType Type; + + public TextBlock (TextBlockType type, string content) + { + Content = content; + Type = type; + } + } + class AspGenerator { +#if NET_2_0 + const int READ_BUFFER_SIZE = 8192; + + internal static Regex DirectiveRegex = new Regex (@"<%\s*@(\s*(?\w[\w:]*(?=\W))(\s*(?=)\s*""(?[^""]*)""|\s*(?=)\s*'(?[^']*)'|\s*(?=)\s*(?[^\s%>]*)|(?)(?\s*?)))*\s*?%>", RegexOptions.Compiled | RegexOptions.IgnoreCase); +#endif + static readonly Regex runatServer = new Regex (@"<[\w:\.]+.*?runat=[""']?server[""']?.*?/?>", + RegexOptions.Compiled | RegexOptions.Singleline | + RegexOptions.Multiline | RegexOptions.IgnoreCase | + RegexOptions.CultureInvariant); + + static readonly Regex endOfTag = new Regex (@"", + RegexOptions.Compiled | RegexOptions.Singleline | + RegexOptions.Multiline | RegexOptions.IgnoreCase | + RegexOptions.CultureInvariant); + + static readonly Regex expressionRegex = new Regex (@"<%.*%>", + RegexOptions.Compiled | RegexOptions.Singleline | + RegexOptions.Multiline | RegexOptions.IgnoreCase | + RegexOptions.CultureInvariant); + + static readonly Regex clientCommentRegex = new Regex (@"", + RegexOptions.Compiled | RegexOptions.Singleline | + RegexOptions.Multiline | RegexOptions.IgnoreCase | + RegexOptions.CultureInvariant); + ParserStack pstack; BuilderLocationStack stack; TemplateParser tparser; @@ -192,20 +242,32 @@ namespace System.Web.Compilation bool inForm; bool useOtherTags; TagType lastTag; +#if NET_2_0 + AspComponentFoundry componentFoundry; + Stream inputStream; + public AspGenerator (TemplateParser tparser, AspComponentFoundry componentFoundry) : this (tparser) + { + this.componentFoundry = componentFoundry; + } +#endif + public AspGenerator (TemplateParser tparser) { this.tparser = tparser; text = new StringBuilder (); stack = new BuilderLocationStack (); + +#if !NET_2_0 rootBuilder = new RootBuilder (tparser); - stack.Push (rootBuilder, null); tparser.RootBuilder = rootBuilder; + stack.Push (rootBuilder, null); +#endif pstack = new ParserStack (); } public RootBuilder RootBuilder { - get { return tparser.RootBuilder; } + get { return rootBuilder; } } public AspParser Parser { @@ -225,6 +287,236 @@ namespace System.Web.Compilation return tparser.PageParserFilter; } } + + // KLUDGE WARNING + // + // The kludge to determine the base type of the to-be-generated ASP.NET class is + // very unfortunate but with our current parser it is, unfortunately, necessary. The + // reason for reading the entire file into memory and parsing it with a regexp is + // that we need to read the main directive (i.e. <%@Page %>, <%@Control %> etc), + // pass it to the page parser filter if it exists, and finally read the inherits + // attribute of the directive to get access to the base type of the class to be + // generated. On that type we check whether it is decorated with the + // FileLevelControlBuilder attribute and, if yes, use the indicated type as the + // RootBuilder. This is necessary for the ASP.NET MVC views using the "generic" + // inherits declaration to work properly. Our current parser is not able to parse + // the input file out of sequence (i.e. directives first, then the rest) so we need + // to do what we do below, alas. + Hashtable GetDirectiveAttributesDictionary (string skipKeyName, CaptureCollection names, CaptureCollection values) + { + var ret = new Hashtable (StringComparer.InvariantCultureIgnoreCase); + + int index = 0; + string keyName; + foreach (Capture c in names) { + keyName = c.Value; + if (String.Compare (skipKeyName, keyName, StringComparison.OrdinalIgnoreCase) == 0) { + index++; + continue; + } + + ret.Add (c.Value, values [index++].Value); + } + + return ret; + } + + string GetDirectiveName (CaptureCollection names) + { + string val; + foreach (Capture c in names) { + val = c.Value; + if (Directive.IsDirective (val)) + return val; + } + + return tparser.DefaultDirectiveName; + } + + int GetLineNumberForIndex (string fileContents, int index) + { + int line = 1; + char c; + bool foundCR = false; + + for (int pos = 0; pos < index; pos++) { + c = fileContents [pos]; + if (c == '\n' || foundCR) { + line++; + foundCR = false; + } + + foundCR = (c == '\r'); + } + + return line; + } + + int GetNumberOfLinesForRange (string fileContents, int index, int length) + { + int lines = 0; + int stop = index + length; + char c; + bool foundCR = false; + + for (int pos = index; pos < stop; pos++) { + c = fileContents [pos]; + if (c == '\n' || foundCR) { + lines++; + foundCR = false; + } + + foundCR = (c == '\r'); + } + + return lines; + } + + Type GetInheritedType (string fileContents, string filename) + { + MatchCollection matches = DirectiveRegex.Matches (fileContents); + if (matches == null || matches.Count == 0) + return null; + + string wantedDirectiveName = tparser.DefaultDirectiveName.ToLower (); + string directiveName; + GroupCollection groups; + CaptureCollection ccNames; + + foreach (Match match in matches) { + groups = match.Groups; + if (groups.Count < 6) + continue; + + ccNames = groups [3].Captures; + directiveName = GetDirectiveName (ccNames); + if (String.IsNullOrEmpty (directiveName)) + continue; + + if (String.Compare (directiveName.ToLower (), wantedDirectiveName, StringComparison.Ordinal) != 0) + continue; + + var loc = new Location (null); + int index = match.Index; + + loc.Filename = filename; + loc.BeginLine = GetLineNumberForIndex (fileContents, index); + loc.EndLine = loc.BeginLine + GetNumberOfLinesForRange (fileContents, index, match.Length); + + tparser.Location = loc; + tparser.allowedMainDirectives = 2; + tparser.AddDirective (wantedDirectiveName, GetDirectiveAttributesDictionary (wantedDirectiveName, ccNames, groups [5].Captures)); + + return tparser.BaseType; + } + + return null; + } + + string ReadFileContents (Stream inputStream, string filename) + { + string ret = null; + + if (inputStream != null) { + if (inputStream.CanSeek) { + long curPos = inputStream.Position; + inputStream.Seek (0, SeekOrigin.Begin); + + Encoding enc = WebEncoding.FileEncoding; + StringBuilder sb = new StringBuilder (); + byte[] buffer = new byte [READ_BUFFER_SIZE]; + int nbytes; + + while ((nbytes = inputStream.Read (buffer, 0, READ_BUFFER_SIZE)) > 0) + sb.Append (enc.GetString (buffer, 0, nbytes)); + inputStream.Seek (curPos, SeekOrigin.Begin); + + ret = sb.ToString (); + sb.Length = 0; + sb.Capacity = 0; + } else { + FileStream fs = inputStream as FileStream; + if (fs != null) { + string fname = fs.Name; + try { + if (File.Exists (fname)) + ret = File.ReadAllText (fname); + } catch { + // ignore + } + } + } + } + + if (ret == null && !String.IsNullOrEmpty (filename) && String.Compare (filename, "@@inner_string@@", StringComparison.Ordinal) != 0) { + try { + if (File.Exists (filename)) + ret = File.ReadAllText (filename); + } catch { + // ignore + } + } + + return ret; + } + + Type GetRootBuilderType (Stream inputStream, string filename) + { + Type ret = null; + string fileContents; + + if (tparser != null) + fileContents = ReadFileContents (inputStream, filename); + else + fileContents = null; + + if (!String.IsNullOrEmpty (fileContents)) { + Type inheritedType = GetInheritedType (fileContents, filename); + fileContents = null; + if (inheritedType != null) { + FileLevelControlBuilderAttribute attr; + + try { + object[] attrs = inheritedType.GetCustomAttributes (typeof (FileLevelControlBuilderAttribute), true); + if (attrs != null && attrs.Length > 0) + attr = attrs [0] as FileLevelControlBuilderAttribute; + else + attr = null; + } catch { + attr = null; + } + + ret = attr != null ? attr.BuilderType : null; + } + } + + if (ret == null) { + if (tparser is PageParser) + return typeof (FileLevelPageControlBuilder); + else if (tparser is UserControlParser) + return typeof (FileLevelUserControlBuilder); + else + return typeof (RootBuilder); + } else + return ret; + } + + void CreateRootBuilder (Stream inputStream, string filename) + { + if (rootBuilder != null) + return; + + Type rootBuilderType = GetRootBuilderType (inputStream, filename); + rootBuilder = Activator.CreateInstance (rootBuilderType) as RootBuilder; + if (rootBuilder == null) + throw new HttpException ("Cannot create an instance of file-level control builder."); + rootBuilder.Init (tparser, null, null, null, null, null); + if (componentFoundry != null) + rootBuilder.Foundry = componentFoundry; + + stack.Push (rootBuilder, null); + tparser.RootBuilder = rootBuilder; + } #endif BaseCompiler GetCompilerFromType () @@ -255,6 +547,7 @@ namespace System.Web.Compilation #if NET_2_0 parser.ParsingComplete += new ParsingCompleteHandler (ParsingCompleted); tparser.AspGenerator = this; + CreateRootBuilder (inputStream, filename); #endif if (!pstack.Push (parser)) throw new ParseException (Location, "Infinite recursion detected including file: " + filename); @@ -266,9 +559,6 @@ namespace System.Web.Compilation tparser.AddDependency (arvp); } -#if NET_2_0 - tparser.MD5Checksum = parser.MD5Checksum; -#endif } #if NET_2_0 @@ -300,15 +590,19 @@ namespace System.Web.Compilation if (text.Length > 0) FlushText (); +#if NET_2_0 + tparser.MD5Checksum = pstack.Parser.MD5Checksum; +#endif pstack.Pop (); #if DEBUG - PrintTree (rootBuilder, 0); + PrintTree (RootBuilder, 0); #endif if (stack.Count > 1 && pstack.Count == 0) throw new ParseException (stack.Builder.Location, "Expecting " + stack.Builder); + } finally { if (reader != null) reader.Close (); @@ -317,6 +611,9 @@ namespace System.Web.Compilation public void Parse (Stream stream, string filename, bool doInitParser) { +#if NET_2_0 + inputStream = stream; +#endif Parse (new StreamReader (stream, WebEncoding.FileEncoding), filename, doInitParser); } @@ -446,9 +743,6 @@ namespace System.Web.Compilation // The kludge supports only self-closing tags inside attributes. // // KLUDGE WARNING!! - static readonly Regex runatServer=new Regex (@"<[\w:\.]+.*?runat=[""']?server[""']?.*?/>", - RegexOptions.Compiled | RegexOptions.Singleline | - RegexOptions.Multiline | RegexOptions.IgnoreCase); bool ProcessTagsInAttributes (ILocation location, string tagid, TagAttributes attributes, TagType type) { if (attributes == null || attributes.Count == 0) @@ -464,8 +758,10 @@ namespace System.Web.Compilation sb.AppendFormat ("\t<{0}", tagid); foreach (string key in attributes.Keys) { value = attributes [key] as string; - if (value == null || value.Length < 16) // optimization + if (value == null || value.Length < 16) { // optimization + sb.AppendFormat (" {0}=\"{1}\"", key, value); continue; + } match = runatServer.Match (attributes [key] as string); if (!match.Success) { @@ -481,13 +777,14 @@ namespace System.Web.Compilation group = match.Groups [0]; index = group.Index; length = group.Length; - - if (index > 0) - TextParsed (location, String.Format (" {0}=\"{1}", key, value.Substring (0, index))); - FlushText (); + + TextParsed (location, String.Format (" {0}=\"{1}", key, index > 0 ? value.Substring (0, index) : String.Empty));; + FlushText (); ParseAttributeTag (group.Value); if (index + length < value.Length) TextParsed (location, value.Substring (index + length) + "\""); + else + TextParsed (location, "\""); } if (type == TagType.SelfClosing) sb.Append ("/>"); @@ -518,12 +815,42 @@ namespace System.Web.Compilation if (pfilter == null) return; - pfilter.ParseComplete (rootBuilder); + pfilter.ParseComplete (RootBuilder); } #endif + + void CheckIfIncludeFileIsSecure (string filePath) + { + if (filePath == null || filePath.Length == 0) + return; + + // a bit slow, but fully portable + string newdir = null; + Exception exception = null; + try { + string origdir = Directory.GetCurrentDirectory (); + Directory.SetCurrentDirectory (Path.GetDirectoryName (filePath)); + newdir = Directory.GetCurrentDirectory (); + Directory.SetCurrentDirectory (origdir); + if (newdir [newdir.Length - 1] != '/') + newdir += "/"; + } catch (DirectoryNotFoundException) { + return; // will be converted into 404 + } catch (FileNotFoundException) { + return; // as above + } catch (Exception ex) { + // better safe than sorry + exception = ex; + } + + if (exception != null || !StrUtils.StartsWith (newdir, HttpRuntime.AppDomainAppPath)) + throw new ParseException (Location, "Files above the application's root directory cannot be included."); + } void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes) { + bool tagIgnored; + this.location = new Location (location); if (tparser != null) tparser.Location = location; @@ -533,10 +860,12 @@ namespace System.Web.Compilation if (0 == String.Compare (tagid, "script", true, CultureInfo.InvariantCulture)) { bool in_script = (inScript || ignore_text); - if (in_script || (tagtype != TagType.Close && attributes != null)) { - if ((in_script || attributes.IsRunAtServer ()) && ProcessScript (tagtype, attributes)) + if (in_script) { + if (ProcessScript (tagtype, attributes)) + return; + } else + if (ProcessScript (tagtype, attributes)) return; - } } lastTag = tagtype; @@ -548,8 +877,9 @@ namespace System.Web.Compilation tparser.AddDirective (tagid, attributes.GetDictionary (null)); break; case TagType.Tag: - if (ProcessTag (location, tagid, attributes, tagtype)) { - useOtherTags = true; + if (ProcessTag (location, tagid, attributes, tagtype, out tagIgnored)) { + if (!tagIgnored) + useOtherTags = true; break; } @@ -573,7 +903,7 @@ namespace System.Web.Compilation break; case TagType.SelfClosing: int count = stack.Count; - if (!ProcessTag (location, tagid, attributes, tagtype)) { + if (!ProcessTag (location, tagid, attributes, tagtype, out tagIgnored) && !tagIgnored) { string plainText = location.PlainText; if (!ProcessTagsInAttributes (location, tagid, attributes, TagType.SelfClosing)) TextParsed (location, plainText); @@ -618,6 +948,7 @@ namespace System.Web.Compilation Parse (tparser.MapPath (file), true); } else { string includeFilePath = GetIncludeFilePath (tparser.ParserDir, file); + CheckIfIncludeFileIsSecure (includeFilePath); tparser.PushIncludeDir (Path.GetDirectoryName (includeFilePath)); try { Parse (includeFilePath, true); @@ -657,22 +988,104 @@ namespace System.Web.Compilation return Path.GetFullPath (Path.Combine (basedir, filename)); } + + delegate bool CheckBlockEnd (string text); + + bool CheckTagEndNeeded (string text) + { + return !text.EndsWith ("/>"); + } + +#if NET_2_0 + List +#else + ArrayList +#endif + FindRegexBlocks (Regex rxStart, Regex rxEnd, CheckBlockEnd checkEnd, IList blocks, TextBlockType typeForMatches, bool discardBlocks) + { +#if NET_2_0 + var ret = new List (); +#else + ArrayList ret = new ArrayList (); +#endif + + foreach (TextBlock block in blocks) { + if (block.Type != TextBlockType.Verbatim) { + ret.Add (block); + continue; + } + + int lastIndex = 0, index; + MatchCollection matches = rxStart.Matches (block.Content); + bool foundMatches = matches.Count > 0; + foreach (Match match in matches) { + foundMatches = true; + index = match.Index; + if (lastIndex < index) + ret.Add (new TextBlock (TextBlockType.Verbatim, block.Content.Substring (lastIndex, index - lastIndex))); + + string value = match.Value; + if (rxEnd != null && checkEnd (value)) { + int startFrom = index + value.Length; + Match m = rxEnd.Match (block.Content, startFrom); + if (m.Success) + value += block.Content.Substring (startFrom, m.Index - startFrom) + m.Value; + } + + if (!discardBlocks) + ret.Add (new TextBlock (typeForMatches, value)); + lastIndex = index + value.Length; + } + + if (lastIndex > 0 && lastIndex < block.Content.Length) + ret.Add (new TextBlock (TextBlockType.Verbatim, block.Content.Substring (lastIndex))); + + if (!foundMatches) + ret.Add (block); + } + + return ret; + } + IList SplitTextIntoBlocks (string text) + { +#if NET_2_0 + var ret = new List (); +#else + ArrayList ret = new ArrayList (); +#endif + + ret.Add (new TextBlock (TextBlockType.Verbatim, text)); + ret = FindRegexBlocks (runatServer, endOfTag, CheckTagEndNeeded, ret, TextBlockType.Tag, false); + ret = FindRegexBlocks (expressionRegex, null, null, ret, TextBlockType.Expression, false); + + return ret; + } + void TextParsed (ILocation location, string text) { if (ignore_text) return; - if (text.IndexOf ("<%") != -1 && !inScript) { - if (this.text.Length > 0) - FlushText (true); - CodeRenderParser r = new CodeRenderParser (text, stack.Builder); - r.AddChildren (); - return; + IList blocks = SplitTextIntoBlocks (text); + foreach (TextBlock block in blocks) { + switch (block.Type) { + case TextBlockType.Verbatim: + this.text.Append (block.Content); + break; + + case TextBlockType.Expression: + if (this.text.Length > 0) + FlushText (true); + CodeRenderParser r = new CodeRenderParser (block.Content, stack.Builder); + r.AddChildren (this); + break; + + case TextBlockType.Tag: + ParseAttributeTag (block.Content); + break; + } } - - this.text.Append (text); - //PrintLocation (location); } void FlushText () @@ -751,7 +1164,7 @@ namespace System.Web.Compilation if (!typeof (System.Web.UI.WebControls.Content).IsAssignableFrom (cb.ControlType)) return true; - if (BuilderHasOtherThan (typeof (System.Web.UI.WebControls.Content), rootBuilder)) + if (BuilderHasOtherThan (typeof (System.Web.UI.WebControls.Content), RootBuilder)) return false; return true; @@ -768,8 +1181,9 @@ namespace System.Web.Compilation parent.AppendSubBuilder (builder); } - bool ProcessTag (ILocation location, string tagid, TagAttributes atts, TagType tagtype) + bool ProcessTag (ILocation location, string tagid, TagAttributes atts, TagType tagtype, out bool ignored) { + ignored = false; if (isApplication) { if (String.Compare (tagid, "object", true, CultureInfo.InvariantCulture) != 0) throw new ParseException (location, "Invalid tag for application file."); @@ -777,6 +1191,13 @@ namespace System.Web.Compilation ControlBuilder parent = stack.Builder; ControlBuilder builder = null; + if (parent != null && parent.ControlType == typeof (HtmlTable) && + (String.Compare (tagid, "thead", true, CultureInfo.InvariantCulture) == 0 || + String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0)) { + ignored = true; + return true; + } + Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash; if (stack.Count > 1) { try { @@ -788,15 +1209,22 @@ namespace System.Web.Compilation } } - if (builder == null && atts != null && atts.IsRunAtServer ()) { + bool runatServer = atts != null && atts.IsRunAtServer (); + if (builder == null && runatServer) { string id = htable ["id"] as string; if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id)) throw new ParseException (Location, "'" + id + "' is not a valid identifier"); try { - builder = rootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location); + builder = RootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location); } catch (TypeLoadException e) { throw new ParseException (Location, "Type not found.", e); + } catch (HttpException e) { + CompilationException inner = e.InnerException as CompilationException; + if (inner != null) + throw inner; + + throw new ParseException (Location, e.Message, e); } catch (Exception e) { throw new ParseException (Location, e.Message, e); } @@ -805,6 +1233,11 @@ namespace System.Web.Compilation if (builder == null) return false; + // This is as good as we can do for now - if the parsed location contains + // both expressions and code render blocks then we're out of luck... + string plainText = location.PlainText; + if (!runatServer && plainText.IndexOf ("<%$") == -1&& plainText.IndexOf ("<%") > -1) + return false; #if NET_2_0 PageParserFilter pfilter = PageParserFilter; if (pfilter != null && !pfilter.AllowControl (builder.ControlType, builder)) @@ -831,7 +1264,7 @@ namespace System.Web.Compilation } else { if (!isApplication && builder is ObjectTagBuilder) { ObjectTagBuilder ot = (ObjectTagBuilder) builder; - if (ot.Scope != null && ot.Scope != "") + if (ot.Scope != null && ot.Scope.Length > 0) throw new ParseException (location, "Scope not allowed here"); if (tagtype == TagType.Tag) { @@ -866,7 +1299,7 @@ namespace System.Web.Compilation CheckLanguage (language); string src = (string) attributes ["src"]; if (src != null) { - if (src == "") + if (src.Length == 0) throw new ParseException (Parser, "src cannot be an empty string"); @@ -925,6 +1358,9 @@ namespace System.Web.Compilation } return true; } + + if (current.ControlType == typeof (HtmlTable) && String.Compare (tagid, "thead", true, CultureInfo.InvariantCulture) == 0) + return true; if (0 != String.Compare (tagid, btag, true, CultureInfo.InvariantCulture)) return false; @@ -955,13 +1391,13 @@ namespace System.Web.Compilation CodeConstructType MapTagTypeToConstructType (TagType tagtype) { switch (tagtype) { - case TagType.DataBinding: + case TagType.CodeRenderExpression: return CodeConstructType.ExpressionSnippet; case TagType.CodeRender: return CodeConstructType.CodeSnippet; - case TagType.CodeRenderExpression: + case TagType.DataBinding: return CodeConstructType.DataBindingSnippet; default: @@ -974,7 +1410,16 @@ namespace System.Web.Compilation { #if NET_2_0 PageParserFilter pfilter = PageParserFilter; - if (pfilter != null && (!pfilter.AllowCode || !pfilter.ProcessCodeConstruct (MapTagTypeToConstructType (tagtype), code))) + // LAMESPEC: + // + // http://msdn.microsoft.com/en-us/library/system.web.ui.pageparserfilter.processcodeconstruct.aspx + // + // The above page says if false is returned then we should NOT process the + // code further, wheras in reality it's the other way around. The + // ProcessCodeConstruct return value means whether or not the filter + // _processed_ the code. + // + if (pfilter != null && (!pfilter.AllowCode || pfilter.ProcessCodeConstruct (MapTagTypeToConstructType (tagtype), code))) return true; #endif ControlBuilder b = null; @@ -1004,7 +1449,7 @@ namespace System.Web.Compilation return; #if NET_2_0 - CompilationSection section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation"); + CompilationSection section = (CompilationSection) WebConfigurationManager.GetWebApplicationSection ("system.web/compilation"); if (section.Compilers[tparser.Language] != section.Compilers[lang]) #else CompilationConfiguration cfg = CompilationConfiguration.GetInstance (HttpContext.Current); @@ -1020,22 +1465,59 @@ namespace System.Web.Compilation { string str; ControlBuilder builder; - + AspGenerator generator; + public CodeRenderParser (string str, ControlBuilder builder) { this.str = str; this.builder = builder; } - public void AddChildren () + public void AddChildren (AspGenerator generator) { + this.generator = generator; int index = str.IndexOf ("<%"); - if (index > 0) { - TextParsed (null, str.Substring (0, index)); - str = str.Substring (index); - } + if (index > 0) + DoParseExpressions (str); + else + DoParse (str); + } - AspParser parser = new AspParser ("@@inner_string@@", new StringReader (str)); + void DoParseExpressions (string str) + { + int startIndex = 0, index = 0; + Regex codeDirective = new Regex ("(<%(?!@)(?.*?)%>)|(<[\\w:\\.]+.*?runat=[\"']?server[\"']?.*?/>)", + RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant); + Match match; + int strLen = str.Length; + + while (index > -1 && startIndex < strLen) { + match = codeDirective.Match (str, index); + + if (match.Success) { + string value = match.Value; + index = match.Index; + if (index > startIndex) + TextParsed (null, str.Substring (startIndex, index - startIndex)); + DoParse (value); + index += value.Length; + startIndex = index; + } else + break; + + if (index < strLen) + index = str.IndexOf ('<', index); + else + break; + } + + if (startIndex < strLen) + TextParsed (null, str.Substring (startIndex)); + } + + void DoParse (string str) + { + AspParser parser = new AspParser ("@@nested_tag@@", new StringReader (str)); parser.Error += new ParseErrorHandler (ParseError); parser.TagParsed += new TagParsedHandler (TagParsed); parser.TextParsed += new TextParsedHandler (TextParsed); @@ -1044,14 +1526,34 @@ namespace System.Web.Compilation void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes) { - if (tagtype == TagType.CodeRender) - builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location)); - else if (tagtype == TagType.CodeRenderExpression) - builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location)); - else if (tagtype == TagType.DataBinding) - builder.AppendSubBuilder (new DataBindingBuilder (tagid, location)); - else - builder.AppendLiteralString (location.PlainText); + switch (tagtype) { + case TagType.CodeRender: + builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location)); + break; + + case TagType.CodeRenderExpression: + builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location)); + break; + + case TagType.DataBinding: + builder.AppendSubBuilder (new DataBindingBuilder (tagid, location)); + break; + + case TagType.Tag: + case TagType.SelfClosing: + case TagType.Close: + if (generator != null) + generator.TagParsed (location, tagtype, tagid, attributes); + else + goto default; + break; + + default: + string text = location.PlainText; + if (text != null && text.Trim ().Length > 0) + builder.AppendLiteralString (text); + break; + } } void TextParsed (ILocation location, string text)