//
// 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)
//
//
using System.Web.UI.HtmlControls;
using System.Web.Util;
+#if NET_2_0
+using System.Collections.Generic;
+#endif
+
namespace System.Web.Compilation
{
class BuilderLocation
}
}
+ 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
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;
void CreateRootBuilder (Stream inputStream, string filename)
{
+ if (rootBuilder != null)
+ return;
+
Type rootBuilderType = GetRootBuilderType (inputStream, filename);
rootBuilder = Activator.CreateInstance (rootBuilderType) as RootBuilder;
if (rootBuilder == null)
// 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)
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)
{
Parse (tparser.MapPath (file), true);
} else {
string includeFilePath = GetIncludeFilePath (tparser.ParserDir, file);
+ CheckIfIncludeFileIsSecure (includeFilePath);
tparser.PushIncludeDir (Path.GetDirectoryName (includeFilePath));
try {
Parse (includeFilePath, true);
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 ()
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);
}
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");
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: