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=38155e22afa5433f10ac1c67a10dfdf0a9a25607;hpb=6e8c180f096f53d78617e08974109f0da25f0b10;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 38155e22afa..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) // // @@ -34,6 +35,7 @@ using System.CodeDom.Compiler; using System.Globalization; using System.IO; using System.Text; +using System.Text.RegularExpressions; using System.Web.Caching; using System.Web.Configuration; using System.Web.Hosting; @@ -41,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 @@ -176,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; @@ -191,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 { @@ -214,6 +277,247 @@ namespace System.Web.Compilation public string Filename { get { return pstack.Filename; } } + +#if NET_2_0 + PageParserFilter PageParserFilter { + get { + if (tparser == null) + return null; + + 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 () { @@ -240,6 +544,11 @@ namespace System.Web.Compilation parser.Error += new ParseErrorHandler (ParseError); parser.TagParsed += new TagParsedHandler (TagParsed); parser.TextParsed += new TextParsedHandler (TextParsed); +#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); @@ -250,9 +559,6 @@ namespace System.Web.Compilation tparser.AddDependency (arvp); } -#if NET_2_0 - tparser.MD5Checksum = parser.MD5Checksum; -#endif } #if NET_2_0 @@ -284,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, + throw new ParseException (stack.Builder.Location, "Expecting " + stack.Builder); + } finally { if (reader != null) reader.Close (); @@ -301,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); } @@ -393,7 +706,7 @@ namespace System.Web.Compilation string i = new string ('\t', indent); Console.Write (i); Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}", - builder, builder.ID, builder.ControlType, builder.parentBuilder); + builder, builder.ID, builder.ControlType, builder.ParentBuilder); if (builder.Children != null) foreach (object o in builder.Children) { @@ -419,8 +732,125 @@ namespace System.Web.Compilation throw new ParseException (location, message); } + // KLUDGE WARNING!! + // + // The code below (ProcessTagsInAttributes, ParseAttributeTag) serves the purpose to work + // around a limitation of the current asp.net parser which is unable to parse server + // controls inside client tag attributes. Since the architecture of the current + // parser does not allow for clean solution of this problem, hence the kludge + // below. It will be gone as soon as the parser is rewritten. + // + // The kludge supports only self-closing tags inside attributes. + // + // KLUDGE WARNING!! + bool ProcessTagsInAttributes (ILocation location, string tagid, TagAttributes attributes, TagType type) + { + if (attributes == null || attributes.Count == 0) + return false; + + Match match; + Group group; + string value; + bool retval = false; + int index, length; + StringBuilder sb = new StringBuilder (); + + sb.AppendFormat ("\t<{0}", tagid); + foreach (string key in attributes.Keys) { + value = attributes [key] as string; + if (value == null || value.Length < 16) { // optimization + sb.AppendFormat (" {0}=\"{1}\"", key, value); + continue; + } + + match = runatServer.Match (attributes [key] as string); + if (!match.Success) { + sb.AppendFormat (" {0}=\"{1}\"", key, value); + continue; + } + if (sb.Length > 0) { + TextParsed (location, sb.ToString ()); + sb.Length = 0; + } + + retval = true; + group = match.Groups [0]; + index = group.Index; + length = group.Length; + + 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 ("/>"); + else + sb.Append (">"); + + if (retval && sb.Length > 0) + TextParsed (location, sb.ToString ()); + + return retval; + } + + void ParseAttributeTag (string code) + { + AspParser parser = new AspParser ("@@attribute_tag@@", new StringReader (code)); + parser.Error += new ParseErrorHandler (ParseError); + parser.TagParsed += new TagParsedHandler (TagParsed); + parser.TextParsed += new TextParsedHandler (TextParsed); + parser.Parse (); + if (text.Length > 0) + FlushText (); + } + +#if NET_2_0 + void ParsingCompleted () + { + PageParserFilter pfilter = PageParserFilter; + if (pfilter == null) + return; + + 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; @@ -430,23 +860,26 @@ 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; switch (tagtype) { case TagType.Directive: - if (tagid == "") + if (tagid.Length == 0) tagid = tparser.DefaultDirectiveName; tparser.AddDirective (tagid, attributes.GetDictionary (null)); break; case TagType.Tag: - if (ProcessTag (tagid, attributes, tagtype)) { - useOtherTags = true; + if (ProcessTag (location, tagid, attributes, tagtype, out tagIgnored)) { + if (!tagIgnored) + useOtherTags = true; break; } @@ -455,7 +888,11 @@ namespace System.Web.Compilation stack.Builder.OtherTags.Add (tagid); } - TextParsed (location, location.PlainText); + { + string plainText = location.PlainText; + if (!ProcessTagsInAttributes (location, tagid, attributes, TagType.Tag)) + TextParsed (location, plainText); + } break; case TagType.Close: bool notServer = (useOtherTags && TryRemoveTag (tagid, stack.Builder.OtherTags)); @@ -466,8 +903,10 @@ namespace System.Web.Compilation break; case TagType.SelfClosing: int count = stack.Count; - if (!ProcessTag (tagid, attributes, tagtype)) { - TextParsed (location, location.PlainText); + if (!ProcessTag (location, tagid, attributes, tagtype, out tagIgnored) && !tagIgnored) { + string plainText = location.PlainText; + if (!ProcessTagsInAttributes (location, tagid, attributes, TagType.SelfClosing)) + TextParsed (location, plainText); } else if (stack.Count != count) { CloseControl (tagid); } @@ -509,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); @@ -548,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 () @@ -580,6 +1102,11 @@ namespace System.Web.Compilation return; if (inScript) { +#if NET_2_0 + PageParserFilter pfilter = PageParserFilter; + if (pfilter != null && !pfilter.ProcessCodeConstruct (CodeConstructType.ScriptTag, t)) + return; +#endif tparser.Scripts.Add (new ServerSideScript (t, new System.Web.Compilation.Location (tparser.Location))); return; } @@ -637,15 +1164,26 @@ 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; } #endif + + public void AddControl (Type type, IDictionary attributes) + { + ControlBuilder parent = stack.Builder; + ControlBuilder builder = ControlBuilder.CreateBuilderFromType (tparser, parent, type, null, null, + attributes, location.BeginLine, + location.Filename); + if (builder != null) + parent.AppendSubBuilder (builder); + } - bool ProcessTag (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."); @@ -653,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 { @@ -664,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); } @@ -681,12 +1233,21 @@ 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)) + throw new ParseException (Location, "Control type '" + builder.ControlType + "' not allowed."); + if (!OtherControlsAllowed (builder)) throw new ParseException (Location, "Only Content controls are allowed directly in a content page that contains Content controls."); #endif - builder.location = location; + builder.Location = location; builder.ID = htable ["id"] as string; if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) { if (inForm) @@ -703,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) { @@ -738,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"); @@ -797,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; @@ -807,7 +1371,7 @@ namespace System.Web.Compilation try { current.SetTagInnerText (tagInnerText.ToString ()); } catch (Exception e) { - throw new ParseException (current.location, e.Message, e); + throw new ParseException (current.Location, e.Message, e); } tagInnerText.Length = 0; @@ -823,8 +1387,41 @@ namespace System.Web.Compilation return true; } +#if NET_2_0 + CodeConstructType MapTagTypeToConstructType (TagType tagtype) + { + switch (tagtype) { + case TagType.CodeRenderExpression: + return CodeConstructType.ExpressionSnippet; + + case TagType.CodeRender: + return CodeConstructType.CodeSnippet; + + case TagType.DataBinding: + return CodeConstructType.DataBindingSnippet; + + default: + throw new InvalidOperationException ("Unexpected tag type."); + } + } + +#endif bool ProcessCode (TagType tagtype, string code, ILocation location) { +#if NET_2_0 + PageParserFilter pfilter = PageParserFilter; + // 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; if (tagtype == TagType.CodeRender) b = new CodeRenderBuilder (code, false, location); @@ -852,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); @@ -868,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); @@ -892,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)