Do not skip comments, just pluck expressions/tags from within them
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AspGenerator.cs
index ed61bb8087f7a45dc3c621b4a97872f6787cdfa6..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)
 //
 
 //
@@ -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,6 +182,26 @@ 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
@@ -184,6 +209,26 @@ namespace System.Web.Compilation
                
                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;
@@ -458,6 +503,9 @@ namespace System.Web.Compilation
                
                void CreateRootBuilder (Stream inputStream, string filename)
                {
+                       if (rootBuilder != null)
+                               return;
+                       
                        Type rootBuilderType = GetRootBuilderType (inputStream, filename);
                        rootBuilder = Activator.CreateInstance (rootBuilderType) as RootBuilder;
                        if (rootBuilder == null)
@@ -695,10 +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 |
-                                                            RegexOptions.CultureInvariant);
                bool ProcessTagsInAttributes (ILocation location, string tagid, TagAttributes attributes, TagType type)
                {
                        if (attributes == null || attributes.Count == 0)
@@ -774,6 +818,34 @@ namespace System.Web.Compilation
                        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)
                {
@@ -876,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);
@@ -915,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 <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);
+                       }
+
+                       return ret;
+               }
                
+               IList SplitTextIntoBlocks (string text)
+               {
+#if NET_2_0
+                       var ret = new List <TextBlock> ();
+#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 (this);
-                               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 ()
@@ -1064,6 +1219,12 @@ namespace System.Web.Compilation
                                        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);
                                }
@@ -1138,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");
 
@@ -1230,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: