// // System.Web.Compilation.AspParser // // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) // Marek Habersack // // (C) 2002,2003 Ximian, Inc (http://www.ximian.com) // (C) 2004-2009 Novell, Inc (http://novell.com) // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.ComponentModel; using System.Collections; using System.Globalization; using System.IO; using System.Text; using System.Web.Util; using System.Security.Cryptography; namespace System.Web.Compilation { delegate void ParseErrorHandler (ILocation location, string message); delegate void TextParsedHandler (ILocation location, string text); delegate void TagParsedHandler (ILocation location, TagType tagtype, string id, TagAttributes attributes); delegate void ParsingCompleteHandler (); class AspParser : ILocation { static readonly object errorEvent = new object (); static readonly object tagParsedEvent = new object (); static readonly object textParsedEvent = new object (); static readonly object parsingCompleteEvent = new object(); MD5 checksum; AspTokenizer tokenizer; int beginLine, endLine; int beginColumn, endColumn; int beginPosition, endPosition; string filename; string verbatimID; string fileText; StringReader fileReader; bool _internal; int _internalLineOffset; int _internalPositionOffset; AspParser outer; EventHandlerList events = new EventHandlerList (); public event ParseErrorHandler Error { add { events.AddHandler (errorEvent, value); } remove { events.RemoveHandler (errorEvent, value); } } public event TagParsedHandler TagParsed { add { events.AddHandler (tagParsedEvent, value); } remove { events.RemoveHandler (tagParsedEvent, value); } } public event TextParsedHandler TextParsed { add { events.AddHandler (textParsedEvent, value); } remove { events.RemoveHandler (textParsedEvent, value); } } public event ParsingCompleteHandler ParsingComplete { add { events.AddHandler (parsingCompleteEvent, value); } remove { events.RemoveHandler (parsingCompleteEvent, value); } } public AspParser (string filename, TextReader input) { this.filename = filename; this.fileText = input.ReadToEnd (); this.fileReader = new StringReader (this.fileText); this._internalLineOffset = 0; tokenizer = new AspTokenizer (this.fileReader); } public AspParser (string filename, TextReader input, int startLineOffset, int positionOffset, AspParser outer) : this (filename, input) { this._internal = true; this._internalLineOffset = startLineOffset; this._internalPositionOffset = positionOffset; this.outer = outer; } public byte[] MD5Checksum { get { if (checksum == null) return new byte[0]; return checksum.Hash; } } public int BeginPosition { get { return beginPosition; } } public int EndPosition { get { return endPosition; } } public int BeginLine { get { if (_internal) return beginLine + _internalLineOffset; return beginLine; } } public int BeginColumn { get { return beginColumn; } } public int EndLine { get { if (_internal) return endLine + _internalLineOffset; return endLine; } } public int EndColumn { get { return endColumn; } } public string FileText { get { string ret = null; if (_internal && outer != null) ret = outer.FileText; if (ret == null && fileText != null) ret = fileText; return ret; } } public string PlainText { get { if (beginPosition >= endPosition || fileText == null) return null; string text = FileText; int start, len; if (_internal && outer != null) { start = beginPosition + _internalPositionOffset; len = (endPosition + _internalPositionOffset) - start; } else { start = beginPosition; len = endPosition - beginPosition; } if (text != null) return text.Substring (start, len); return null; } } public string Filename { get { if (_internal && outer != null) return outer.Filename; return filename; } } public string VerbatimID { set { tokenizer.Verbatim = true; verbatimID = value; } } bool Eat (int expected_token) { int token = tokenizer.get_token (); if (token != expected_token) { tokenizer.put_back (); return false; } endLine = tokenizer.EndLine; endColumn = tokenizer.EndColumn; return true; } void BeginElement () { beginLine = tokenizer.BeginLine; beginColumn = tokenizer.BeginColumn; beginPosition = tokenizer.Position - 1; } void EndElement () { endLine = tokenizer.EndLine; endColumn = tokenizer.EndColumn; endPosition = tokenizer.Position; } public void Parse () { if (tokenizer == null) { OnError ("AspParser not initialized properly."); return; } int token; string id; TagAttributes attributes; TagType tagtype = TagType.Text; StringBuilder text = new StringBuilder (); try { while ((token = tokenizer.get_token ()) != Token.EOF) { BeginElement (); if (tokenizer.Verbatim){ string end_verbatim = ""; string verbatim_text = GetVerbatim (token, end_verbatim); if (verbatim_text == null) OnError ("Unexpected EOF processing " + verbatimID); tokenizer.Verbatim = false; EndElement (); endPosition -= end_verbatim.Length; OnTextParsed (verbatim_text); beginPosition = endPosition; endPosition += end_verbatim.Length; OnTagParsed (TagType.Close, verbatimID, null); continue; } if (token == '<') { GetTag (out tagtype, out id, out attributes); EndElement (); if (tagtype == TagType.ServerComment) continue; if (tagtype == TagType.Text) OnTextParsed (id); else OnTagParsed (tagtype, id, attributes); continue; } if (tokenizer.Value.Trim ().Length == 0 && tagtype == TagType.Directive) { continue; } text.Length = 0; do { text.Append (tokenizer.Value); token = tokenizer.get_token (); } while (token != '<' && token != Token.EOF); tokenizer.put_back (); EndElement (); OnTextParsed (text.ToString ()); } } finally { if (fileReader != null) { fileReader.Close (); fileReader = null; } checksum = tokenizer.Checksum; tokenizer = null; } OnParsingComplete (); } bool GetInclude (string str, out string pathType, out string filename) { pathType = null; filename = null; str = str.Substring (2).Trim (); int len = str.Length; int lastQuote = str.LastIndexOf ('"'); if (len < 10 || lastQuote != len - 1) return false; if (!StrUtils.StartsWith (str, "#include ", true)) return false; str = str.Substring (9).Trim (); bool isfile = (StrUtils.StartsWith (str ,"file", true)); if (!isfile && !StrUtils.StartsWith (str, "virtual", true)) return false; pathType = (isfile) ? "file" : "virtual"; if (str.Length < pathType.Length + 3) return false; str = str.Substring (pathType.Length).Trim (); if (str.Length < 3 || str [0] != '=') return false; int index = 1; for (; index < str.Length; index++) { if (Char.IsWhiteSpace (str [index])) continue; else if (str [index] == '"') break; } if (index == str.Length || index == lastQuote) return false; str = str.Substring (index); if (str.Length == 2) { // only quotes OnError ("Empty file name."); return false; } filename = str.Trim ().Substring (index, str.Length - 2); if (filename.LastIndexOf ('"') != -1) return false; // file=""" -> no error return true; } void GetTag (out TagType tagtype, out string id, out TagAttributes attributes) { int token = tokenizer.get_token (); tagtype = TagType.ServerComment; id = null; attributes = null; switch (token){ case '%': GetServerTag (out tagtype, out id, out attributes); break; case '/': if (!Eat (Token.IDENTIFIER)) OnError ("expecting TAGNAME"); id = tokenizer.Value; if (!Eat ('>')) OnError ("expecting '>'. Got '" + id + "'"); tagtype = TagType.Close; break; case '!': bool double_dash = Eat (Token.DOUBLEDASH); if (double_dash) tokenizer.put_back (); tokenizer.Verbatim = true; string end = double_dash ? "-->" : ">"; string comment = GetVerbatim (tokenizer.get_token (), end); tokenizer.Verbatim = false; if (comment == null) OnError ("Unfinished HTML comment/DTD"); string pathType, filename; if (double_dash && GetInclude (comment, out pathType, out filename)) { tagtype = TagType.Include; attributes = new TagAttributes (); attributes.Add (pathType, filename); } else { tagtype = TagType.Text; id = "')) { tagtype = TagType.SelfClosing; } else if (!Eat ('>')) { if (attributes.IsRunAtServer ()) { OnError ("The server tag is not well formed."); break; } tokenizer.Verbatim = true; attributes.Add (String.Empty, GetVerbatim (tokenizer.get_token (), ">") + ">"); tokenizer.Verbatim = false; } } break; default: string idvalue = null; // This is to handle code like: // // < // if ((char)token == '<') { string odds = tokenizer.Odds; if (odds != null && odds.Length > 0 && Char.IsWhiteSpace (odds [0])) { tokenizer.put_back (); idvalue = odds; } else idvalue = tokenizer.Value; } else idvalue = tokenizer.Value; tagtype = TagType.Text; tokenizer.InTag = false; id = "<" + idvalue; break; } } TagAttributes GetAttributes () { int token; TagAttributes attributes; string id; bool wellFormedForServer = true; attributes = new TagAttributes (); while ((token = tokenizer.get_token ()) != Token.EOF){ if (token == '<' && Eat ('%')) { tokenizer.Verbatim = true; attributes.Add (String.Empty, "<%" + GetVerbatim (tokenizer.get_token (), "%>") + "%>"); tokenizer.Verbatim = false; tokenizer.InTag = true; continue; } if (token != Token.IDENTIFIER) break; id = tokenizer.Value; if (Eat ('=')){ if (Eat (Token.ATTVALUE)){ attributes.Add (id, tokenizer.Value); wellFormedForServer &= tokenizer.AlternatingQuotes; } else if (Eat ('<') && Eat ('%')) { tokenizer.Verbatim = true; attributes.Add (id, "<%" + GetVerbatim (tokenizer.get_token (), "%>") + "%>"); tokenizer.Verbatim = false; tokenizer.InTag = true; } else { OnError ("expected ATTVALUE"); return null; } } else { attributes.Add (id, null); } } tokenizer.put_back (); if (attributes.IsRunAtServer () && !wellFormedForServer) { OnError ("The server tag is not well formed."); return null; } return attributes; } string GetVerbatim (int token, string end) { StringBuilder vb_text = new StringBuilder (); StringBuilder tmp = new StringBuilder (); int i = 0; if (tokenizer.Value.Length > 1){ // May be we have a put_back token that is not a single character vb_text.Append (tokenizer.Value); token = tokenizer.get_token (); } end = end.ToLower (Helpers.InvariantCulture); int repeated = 0; for (int k = 0; k < end.Length; k++) if (end [0] == end [k]) repeated++; while (token != Token.EOF){ if (Char.ToLower ((char) token, Helpers.InvariantCulture) == end [i]){ if (++i >= end.Length) break; tmp.Append ((char) token); token = tokenizer.get_token (); continue; } else if (i > 0) { if (repeated > 1 && i == repeated && (char) token == end [0]) { vb_text.Append ((char) token); token = tokenizer.get_token (); continue; } vb_text.Append (tmp.ToString ()); tmp.Remove (0, tmp.Length); i = 0; } vb_text.Append ((char) token); token = tokenizer.get_token (); } if (token == Token.EOF) OnError ("Expecting " + end + " and got EOF."); return RemoveComments (vb_text.ToString ()); } string RemoveComments (string text) { int end; int start = text.IndexOf ("<%--"); while (start != -1) { end = text.IndexOf ("--%>"); if (end == -1 || end <= start + 1) break; text = text.Remove (start, end - start + 4); start = text.IndexOf ("<%--"); } return text; } void GetServerTag (out TagType tagtype, out string id, out TagAttributes attributes) { string inside_tags; bool old = tokenizer.ExpectAttrValue; tokenizer.ExpectAttrValue = false; if (Eat ('@')){ tokenizer.ExpectAttrValue = old; tagtype = TagType.Directive; id = ""; if (Eat (Token.DIRECTIVE)) id = tokenizer.Value; attributes = GetAttributes (); if (!Eat ('%') || !Eat ('>')) OnError ("expecting '%>'"); return; } if (Eat (Token.DOUBLEDASH)) { tokenizer.ExpectAttrValue = old; tokenizer.Verbatim = true; inside_tags = GetVerbatim (tokenizer.get_token (), "--%>"); tokenizer.Verbatim = false; id = null; attributes = null; tagtype = TagType.ServerComment; return; } tokenizer.ExpectAttrValue = old; bool varname; bool databinding; bool codeRenderEncode; varname = Eat ('='); databinding = !varname && Eat ('#'); codeRenderEncode = !databinding && !varname && Eat (':'); string odds = tokenizer.Odds; tokenizer.Verbatim = true; inside_tags = GetVerbatim (tokenizer.get_token (), "%>"); if (databinding && odds != null && odds.Length > 0) { databinding = false; // We encountered <% #something here %>, this should be passed // verbatim to the compiler inside_tags = '#' + inside_tags; } tokenizer.Verbatim = false; id = inside_tags; attributes = null; if (databinding) tagtype = TagType.DataBinding; else if (varname) tagtype = TagType.CodeRenderExpression; else if (codeRenderEncode) tagtype = TagType.CodeRenderEncode; else tagtype = TagType.CodeRender; } public override string ToString () { StringBuilder sb = new StringBuilder ("AspParser {"); if (filename != null && filename.Length > 0) sb.AppendFormat ("{0}:{1}.{2}", filename, beginLine, beginColumn); sb.Append ('}'); return sb.ToString (); } void OnError (string msg) { ParseErrorHandler eh = events [errorEvent] as ParseErrorHandler; if (eh != null) eh (this, msg); } void OnTagParsed (TagType tagtype, string id, TagAttributes attributes) { TagParsedHandler eh = events [tagParsedEvent] as TagParsedHandler; if (eh != null) eh (this, tagtype, id, attributes); } void OnTextParsed (string text) { TextParsedHandler eh = events [textParsedEvent] as TextParsedHandler; if (eh != null) eh (this, text); } void OnParsingComplete () { ParsingCompleteHandler eh = events [parsingCompleteEvent] as ParsingCompleteHandler; if (eh != null) eh (); } } }