Do not skip comments, just pluck expressions/tags from within them
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AspGenerator.cs
index 14b2629f12927e4f88ca938f72e47dcb4b783e90..5fc857a322d98068851191f1c7e09374d394dba4 100644 (file)
@@ -3,9 +3,10 @@
 //
 // Authors:
 //     Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//      Marek Habersack <mhabersack@novell.com>
 //
 // (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*(?<attrname>\w[\w:]*(?=\W))(\s*(?<equal>=)\s*""(?<attrval>[^""]*)""|\s*(?<equal>=)\s*'(?<attrval>[^']*)'|\s*(?<equal>=)\s*(?<attrval>[^\s%>]*)|(?<equal>)(?<attrval>\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 (@"</[\w:\.]+\s*>",
+                                                           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,17 +559,16 @@ namespace System.Web.Compilation
                                
                                tparser.AddDependency (arvp);
                        }
-#if NET_2_0
-                       tparser.MD5Checksum = parser.MD5Checksum;
-#endif
                }
                
+#if NET_2_0
                void InitParser (string filename)
                {
                        StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
                        InitParser (reader, filename);
                }
-
+#endif
+               
                public void Parse (string file)
                {
 #if ONLY_1_1
@@ -282,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.TagName + "> " + stack.Builder);
+
                        } finally {
                                if (reader != null)
                                        reader.Close ();
@@ -299,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);
                }
                
@@ -347,18 +662,23 @@ namespace System.Web.Compilation
 
                internal static void AddTypeToCache (ArrayList dependencies, string inputFile, Type type)
                {
-                       string [] deps = (string []) dependencies.ToArray (typeof (string));
-                       HttpContext ctx = HttpContext.Current;
-                       HttpRequest req = ctx != null ? ctx.Request : null;
+                       if (type == null || inputFile == null || inputFile.Length == 0)
+                               return;
 
-                       if (req == null)
-                               throw new HttpException ("No current context, cannot compile.");
+                       if (dependencies != null && dependencies.Count > 0) {
+                               string [] deps = (string []) dependencies.ToArray (typeof (string));
+                               HttpContext ctx = HttpContext.Current;
+                               HttpRequest req = ctx != null ? ctx.Request : null;
+                               
+                               if (req == null)
+                                       throw new HttpException ("No current context, cannot compile.");
 
-                       int depLength = deps.Length;
-                       for (int i = 0; i < deps.Length; i++)
-                               deps [i] = req.MapPath (deps [i]);                      
+                               for (int i = 0; i < deps.Length; i++)
+                                       deps [i] = req.MapPath (deps [i]);
 
-                       HttpRuntime.InternalCache.Insert ("@@Type" + inputFile, type, new CacheDependency (deps));
+                               HttpRuntime.InternalCache.Insert ("@@Type" + inputFile, type, new CacheDependency (deps));
+                       } else
+                               HttpRuntime.InternalCache.Insert ("@@Type" + inputFile, type);
                }
                
                public Type GetCompiledType ()
@@ -371,7 +691,7 @@ namespace System.Web.Compilation
                        Parse ();
 
                        BaseCompiler compiler = GetCompilerFromType ();
-
+                       
                        type = compiler.GetCompiledType ();
                        AddTypeToCache (tparser.Dependencies, tparser.InputFile, type);
                        return type;
@@ -386,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) {
@@ -412,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;
@@ -423,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;
                                }
 
@@ -448,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));
@@ -459,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);
                                }
@@ -470,7 +916,6 @@ namespace System.Web.Compilation
                        case TagType.CodeRenderExpression:
                                goto case TagType.CodeRender;
                        case TagType.CodeRender:
-                               lastTag = TagType.CodeRender;
                                if (isApplication)
                                        throw new ParseException (location, "Invalid content for application file.");
                        
@@ -503,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);
@@ -542,27 +988,104 @@ namespace System.Web.Compilation
 
                        return Path.GetFullPath (Path.Combine (basedir, filename));
                }
+
+               delegate bool CheckBlockEnd (string text);
                
-               void TextParsed (ILocation location, string text)
+               bool CheckTagEndNeeded (string text)
                {
-                       if (ignore_text)
-                               return;
+                       return !text.EndsWith ("/>");
+               }
+               
+#if NET_2_0
+               List <TextBlock>
+#else
+               ArrayList
+#endif
+               FindRegexBlocks (Regex rxStart, Regex rxEnd, CheckBlockEnd checkEnd, IList blocks, TextBlockType typeForMatches, bool discardBlocks)
+               {
+#if NET_2_0
+                       var ret = new List <TextBlock> ();
+#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);
+                       }
 
-                       bool isComment = text.StartsWith ("<!--"
+                       return ret;
+               }
+               
+               IList SplitTextIntoBlocks (string text)
+               {
 #if NET_2_0
-                                                         , StringComparison.OrdinalIgnoreCase
+                       var ret = new List <TextBlock> ();
+#else
+                       ArrayList ret = new ArrayList ();
 #endif
-                       );
-                       if (!isComment && text.IndexOf ("<%") != -1 && !inScript) {
-                               if (this.text.Length > 0)
-                                       FlushText (true);
-                               CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
-                               r.AddChildren ();
+
+                       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;
+
+                       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 ()
@@ -579,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;
                        }
@@ -620,8 +1148,8 @@ namespace System.Web.Compilation
                                        if (tmp is System.Web.UI.WebControls.ContentBuilderInternal)
                                                continue;
                                        
-                                       if (!(tmp.ControlType is System.Web.UI.WebControls.Content))
-                                               return true;                                    
+                                       if (tmp.ControlType != typeof (System.Web.UI.WebControls.Content))
+                                               return true;
                                }
                        }
 
@@ -636,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.");
@@ -652,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 {
@@ -663,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);
                                }
@@ -680,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)
@@ -702,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) {
@@ -737,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");
 
@@ -785,7 +1347,7 @@ namespace System.Web.Compilation
                bool CloseControl (string tagid)
                {
                        ControlBuilder current = stack.Builder;
-                       string btag = current.TagName;
+                       string btag = current.OriginalTagName;
                        if (String.Compare (btag, "tbody", true, CultureInfo.InvariantCulture) != 0 &&
                            String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0) {
                                if (!current.ChildrenAsProperties) {
@@ -796,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;
@@ -806,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;
@@ -822,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);
@@ -851,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); 
@@ -867,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 ("(<%(?!@)(?<code>.*?)%>)|(<[\\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);
@@ -891,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)