AspParser.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. //
  2. // System.Web.Compilation.AspParser
  3. //
  4. // Authors:
  5. // Gonzalo Paniagua Javier ([email protected])
  6. // Marek Habersack <[email protected]>
  7. //
  8. // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
  9. // (C) 2004-2009 Novell, Inc (http://novell.com)
  10. //
  11. //
  12. // Permission is hereby granted, free of charge, to any person obtaining
  13. // a copy of this software and associated documentation files (the
  14. // "Software"), to deal in the Software without restriction, including
  15. // without limitation the rights to use, copy, modify, merge, publish,
  16. // distribute, sublicense, and/or sell copies of the Software, and to
  17. // permit persons to whom the Software is furnished to do so, subject to
  18. // the following conditions:
  19. //
  20. // The above copyright notice and this permission notice shall be
  21. // included in all copies or substantial portions of the Software.
  22. //
  23. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  24. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  25. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  26. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  27. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  28. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  29. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  30. //
  31. using System;
  32. using System.ComponentModel;
  33. using System.Collections;
  34. using System.Globalization;
  35. using System.IO;
  36. using System.Text;
  37. using System.Web.Util;
  38. using System.Security.Cryptography;
  39. namespace System.Web.Compilation
  40. {
  41. delegate void ParseErrorHandler (ILocation location, string message);
  42. delegate void TextParsedHandler (ILocation location, string text);
  43. delegate void TagParsedHandler (ILocation location, TagType tagtype, string id, TagAttributes attributes);
  44. #if NET_2_0
  45. delegate void ParsingCompleteHandler ();
  46. #endif
  47. class AspParser : ILocation
  48. {
  49. static readonly object errorEvent = new object ();
  50. static readonly object tagParsedEvent = new object ();
  51. static readonly object textParsedEvent = new object ();
  52. #if NET_2_0
  53. static readonly object parsingCompleteEvent = new object();
  54. MD5 checksum;
  55. #endif
  56. AspTokenizer tokenizer;
  57. int beginLine, endLine;
  58. int beginColumn, endColumn;
  59. int beginPosition, endPosition;
  60. string filename;
  61. string verbatimID;
  62. string fileText;
  63. StringReader fileReader;
  64. EventHandlerList events = new EventHandlerList ();
  65. public event ParseErrorHandler Error {
  66. add { events.AddHandler (errorEvent, value); }
  67. remove { events.RemoveHandler (errorEvent, value); }
  68. }
  69. public event TagParsedHandler TagParsed {
  70. add { events.AddHandler (tagParsedEvent, value); }
  71. remove { events.RemoveHandler (tagParsedEvent, value); }
  72. }
  73. public event TextParsedHandler TextParsed {
  74. add { events.AddHandler (textParsedEvent, value); }
  75. remove { events.RemoveHandler (textParsedEvent, value); }
  76. }
  77. #if NET_2_0
  78. public event ParsingCompleteHandler ParsingComplete {
  79. add { events.AddHandler (parsingCompleteEvent, value); }
  80. remove { events.RemoveHandler (parsingCompleteEvent, value); }
  81. }
  82. #endif
  83. public AspParser (string filename, TextReader input)
  84. {
  85. this.filename = filename;
  86. this.fileText = input.ReadToEnd ();
  87. this.fileReader = new StringReader (this.fileText);
  88. tokenizer = new AspTokenizer (this.fileReader);
  89. }
  90. #if NET_2_0
  91. public byte[] MD5Checksum {
  92. get {
  93. if (checksum == null)
  94. return new byte[0];
  95. return checksum.Hash;
  96. }
  97. }
  98. #endif
  99. public int BeginLine {
  100. get { return beginLine; }
  101. }
  102. public int BeginColumn {
  103. get { return beginColumn; }
  104. }
  105. public int EndLine {
  106. get { return endLine; }
  107. }
  108. public int EndColumn {
  109. get { return endColumn; }
  110. }
  111. public string FileText {
  112. get {
  113. if (fileText != null)
  114. return fileText;
  115. return null;
  116. }
  117. }
  118. public string PlainText {
  119. get {
  120. if (beginPosition >= endPosition || fileText == null)
  121. return null;
  122. return fileText.Substring (beginPosition, endPosition - beginPosition);
  123. }
  124. }
  125. public string Filename {
  126. get { return filename; }
  127. }
  128. public string VerbatimID {
  129. set {
  130. tokenizer.Verbatim = true;
  131. verbatimID = value;
  132. }
  133. }
  134. bool Eat (int expected_token)
  135. {
  136. int token = tokenizer.get_token ();
  137. if (token != expected_token) {
  138. tokenizer.put_back ();
  139. return false;
  140. }
  141. endLine = tokenizer.EndLine;
  142. endColumn = tokenizer.EndColumn;
  143. return true;
  144. }
  145. void BeginElement ()
  146. {
  147. beginLine = tokenizer.BeginLine;
  148. beginColumn = tokenizer.BeginColumn;
  149. beginPosition = tokenizer.Position - 1;
  150. }
  151. void EndElement ()
  152. {
  153. endLine = tokenizer.EndLine;
  154. endColumn = tokenizer.EndColumn;
  155. endPosition = tokenizer.Position;
  156. }
  157. public void Parse ()
  158. {
  159. if (tokenizer == null) {
  160. OnError ("AspParser not initialized properly.");
  161. return;
  162. }
  163. int token;
  164. string id;
  165. TagAttributes attributes;
  166. TagType tagtype = TagType.Text;
  167. StringBuilder text = new StringBuilder ();
  168. try {
  169. while ((token = tokenizer.get_token ()) != Token.EOF) {
  170. BeginElement ();
  171. if (tokenizer.Verbatim){
  172. string end_verbatim = "</" + verbatimID + ">";
  173. string verbatim_text = GetVerbatim (token, end_verbatim);
  174. if (verbatim_text == null)
  175. OnError ("Unexpected EOF processing " + verbatimID);
  176. tokenizer.Verbatim = false;
  177. EndElement ();
  178. endPosition -= end_verbatim.Length;
  179. OnTextParsed (verbatim_text);
  180. beginPosition = endPosition;
  181. endPosition += end_verbatim.Length;
  182. OnTagParsed (TagType.Close, verbatimID, null);
  183. continue;
  184. }
  185. if (token == '<') {
  186. GetTag (out tagtype, out id, out attributes);
  187. EndElement ();
  188. if (tagtype == TagType.ServerComment)
  189. continue;
  190. if (tagtype == TagType.Text)
  191. OnTextParsed (id);
  192. else
  193. OnTagParsed (tagtype, id, attributes);
  194. continue;
  195. }
  196. if (tokenizer.Value.Trim ().Length == 0 && tagtype == TagType.Directive) {
  197. continue;
  198. }
  199. text.Length = 0;
  200. do {
  201. text.Append (tokenizer.Value);
  202. token = tokenizer.get_token ();
  203. } while (token != '<' && token != Token.EOF);
  204. tokenizer.put_back ();
  205. EndElement ();
  206. OnTextParsed (text.ToString ());
  207. }
  208. } finally {
  209. if (fileReader != null) {
  210. fileReader.Close ();
  211. fileReader = null;
  212. }
  213. #if NET_2_0
  214. checksum = tokenizer.Checksum;
  215. #endif
  216. tokenizer = null;
  217. }
  218. #if NET_2_0
  219. OnParsingComplete ();
  220. #endif
  221. }
  222. bool GetInclude (string str, out string pathType, out string filename)
  223. {
  224. pathType = null;
  225. filename = null;
  226. str = str.Substring (2).Trim ();
  227. int len = str.Length;
  228. int lastQuote = str.LastIndexOf ('"');
  229. if (len < 10 || lastQuote != len - 1)
  230. return false;
  231. if (!StrUtils.StartsWith (str, "#include ", true))
  232. return false;
  233. str = str.Substring (9).Trim ();
  234. bool isfile = (StrUtils.StartsWith (str ,"file", true));
  235. if (!isfile && !StrUtils.StartsWith (str, "virtual", true))
  236. return false;
  237. pathType = (isfile) ? "file" : "virtual";
  238. if (str.Length < pathType.Length + 3)
  239. return false;
  240. str = str.Substring (pathType.Length).Trim ();
  241. if (str.Length < 3 || str [0] != '=')
  242. return false;
  243. int index = 1;
  244. for (; index < str.Length; index++) {
  245. if (Char.IsWhiteSpace (str [index]))
  246. continue;
  247. else if (str [index] == '"')
  248. break;
  249. }
  250. if (index == str.Length || index == lastQuote)
  251. return false;
  252. str = str.Substring (index);
  253. if (str.Length == 2) { // only quotes
  254. OnError ("Empty file name.");
  255. return false;
  256. }
  257. filename = str.Trim ().Substring (index, str.Length - 2);
  258. if (filename.LastIndexOf ('"') != -1)
  259. return false; // file=""" -> no error
  260. return true;
  261. }
  262. void GetTag (out TagType tagtype, out string id, out TagAttributes attributes)
  263. {
  264. int token = tokenizer.get_token ();
  265. tagtype = TagType.ServerComment;
  266. id = null;
  267. attributes = null;
  268. switch (token){
  269. case '%':
  270. GetServerTag (out tagtype, out id, out attributes);
  271. break;
  272. case '/':
  273. if (!Eat (Token.IDENTIFIER))
  274. OnError ("expecting TAGNAME");
  275. id = tokenizer.Value;
  276. if (!Eat ('>'))
  277. OnError ("expecting '>'. Got '" + id + "'");
  278. tagtype = TagType.Close;
  279. break;
  280. case '!':
  281. bool double_dash = Eat (Token.DOUBLEDASH);
  282. if (double_dash)
  283. tokenizer.put_back ();
  284. tokenizer.Verbatim = true;
  285. string end = double_dash ? "-->" : ">";
  286. string comment = GetVerbatim (tokenizer.get_token (), end);
  287. tokenizer.Verbatim = false;
  288. if (comment == null)
  289. OnError ("Unfinished HTML comment/DTD");
  290. string pathType, filename;
  291. if (double_dash && GetInclude (comment, out pathType, out filename)) {
  292. tagtype = TagType.Include;
  293. attributes = new TagAttributes ();
  294. attributes.Add (pathType, filename);
  295. } else {
  296. tagtype = TagType.Text;
  297. id = "<!" + comment + end;
  298. }
  299. break;
  300. case Token.IDENTIFIER:
  301. if (this.filename == "@@inner_string@@") {
  302. // Actually not tag but "xxx < yyy" stuff in inner_string!
  303. tagtype = TagType.Text;
  304. tokenizer.InTag = false;
  305. id = "<" + tokenizer.Odds + tokenizer.Value;
  306. } else {
  307. id = tokenizer.Value;
  308. try {
  309. attributes = GetAttributes ();
  310. } catch (Exception e) {
  311. OnError (e.Message);
  312. break;
  313. }
  314. tagtype = TagType.Tag;
  315. if (Eat ('/') && Eat ('>')) {
  316. tagtype = TagType.SelfClosing;
  317. } else if (!Eat ('>')) {
  318. if (attributes.IsRunAtServer ()) {
  319. OnError ("The server tag is not well formed.");
  320. break;
  321. }
  322. tokenizer.Verbatim = true;
  323. attributes.Add ("", GetVerbatim (tokenizer.get_token (), ">") + ">");
  324. tokenizer.Verbatim = false;
  325. }
  326. }
  327. break;
  328. default:
  329. string idvalue = null;
  330. // This is to handle code like:
  331. //
  332. // <asp:ListItem runat="server"> < </asp:ListItem>
  333. //
  334. if ((char)token == '<') {
  335. string odds = tokenizer.Odds;
  336. if (odds != null && odds.Length > 0 && Char.IsWhiteSpace (odds [0])) {
  337. tokenizer.put_back ();
  338. idvalue = odds;
  339. } else
  340. idvalue = tokenizer.Value;
  341. } else
  342. idvalue = tokenizer.Value;
  343. tagtype = TagType.Text;
  344. tokenizer.InTag = false;
  345. id = "<" + idvalue;
  346. break;
  347. }
  348. }
  349. TagAttributes GetAttributes ()
  350. {
  351. int token;
  352. TagAttributes attributes;
  353. string id;
  354. bool wellFormedForServer = true;
  355. attributes = new TagAttributes ();
  356. while ((token = tokenizer.get_token ()) != Token.EOF){
  357. if (token == '<' && Eat ('%')) {
  358. tokenizer.Verbatim = true;
  359. attributes.Add ("", "<%" +
  360. GetVerbatim (tokenizer.get_token (), "%>") + "%>");
  361. tokenizer.Verbatim = false;
  362. tokenizer.InTag = true;
  363. continue;
  364. }
  365. if (token != Token.IDENTIFIER)
  366. break;
  367. id = tokenizer.Value;
  368. if (Eat ('=')){
  369. if (Eat (Token.ATTVALUE)){
  370. attributes.Add (id, tokenizer.Value);
  371. wellFormedForServer &= tokenizer.AlternatingQuotes;
  372. } else if (Eat ('<') && Eat ('%')) {
  373. tokenizer.Verbatim = true;
  374. attributes.Add (id, "<%" +
  375. GetVerbatim (tokenizer.get_token (), "%>") + "%>");
  376. tokenizer.Verbatim = false;
  377. tokenizer.InTag = true;
  378. } else {
  379. OnError ("expected ATTVALUE");
  380. return null;
  381. }
  382. } else {
  383. attributes.Add (id, null);
  384. }
  385. }
  386. tokenizer.put_back ();
  387. if (attributes.IsRunAtServer () && !wellFormedForServer) {
  388. OnError ("The server tag is not well formed.");
  389. return null;
  390. }
  391. return attributes;
  392. }
  393. string GetVerbatim (int token, string end)
  394. {
  395. StringBuilder vb_text = new StringBuilder ();
  396. StringBuilder tmp = new StringBuilder ();
  397. int i = 0;
  398. if (tokenizer.Value.Length > 1){
  399. // May be we have a put_back token that is not a single character
  400. vb_text.Append (tokenizer.Value);
  401. token = tokenizer.get_token ();
  402. }
  403. end = end.ToLower (CultureInfo.InvariantCulture);
  404. int repeated = 0;
  405. for (int k = 0; k < end.Length; k++)
  406. if (end [0] == end [k])
  407. repeated++;
  408. while (token != Token.EOF){
  409. if (Char.ToLower ((char) token, CultureInfo.InvariantCulture) == end [i]){
  410. if (++i >= end.Length)
  411. break;
  412. tmp.Append ((char) token);
  413. token = tokenizer.get_token ();
  414. continue;
  415. } else if (i > 0) {
  416. if (repeated > 1 && i == repeated && (char) token == end [0]) {
  417. vb_text.Append ((char) token);
  418. token = tokenizer.get_token ();
  419. continue;
  420. }
  421. vb_text.Append (tmp.ToString ());
  422. tmp.Remove (0, tmp.Length);
  423. i = 0;
  424. }
  425. vb_text.Append ((char) token);
  426. token = tokenizer.get_token ();
  427. }
  428. if (token == Token.EOF)
  429. OnError ("Expecting " + end + " and got EOF.");
  430. return RemoveComments (vb_text.ToString ());
  431. }
  432. string RemoveComments (string text)
  433. {
  434. int end;
  435. int start = text.IndexOf ("<%--");
  436. while (start != -1) {
  437. end = text.IndexOf ("--%>");
  438. if (end == -1 || end <= start + 1)
  439. break;
  440. text = text.Remove (start, end - start + 4);
  441. start = text.IndexOf ("<%--");
  442. }
  443. return text;
  444. }
  445. void GetServerTag (out TagType tagtype, out string id, out TagAttributes attributes)
  446. {
  447. string inside_tags;
  448. bool old = tokenizer.ExpectAttrValue;
  449. tokenizer.ExpectAttrValue = false;
  450. if (Eat ('@')){
  451. tokenizer.ExpectAttrValue = old;
  452. tagtype = TagType.Directive;
  453. id = "";
  454. if (Eat (Token.DIRECTIVE))
  455. id = tokenizer.Value;
  456. attributes = GetAttributes ();
  457. if (!Eat ('%') || !Eat ('>'))
  458. OnError ("expecting '%>'");
  459. return;
  460. }
  461. if (Eat (Token.DOUBLEDASH)) {
  462. tokenizer.ExpectAttrValue = old;
  463. tokenizer.Verbatim = true;
  464. inside_tags = GetVerbatim (tokenizer.get_token (), "--%>");
  465. tokenizer.Verbatim = false;
  466. id = null;
  467. attributes = null;
  468. tagtype = TagType.ServerComment;
  469. return;
  470. }
  471. tokenizer.ExpectAttrValue = old;
  472. bool varname;
  473. bool databinding;
  474. varname = Eat ('=');
  475. databinding = !varname && Eat ('#');
  476. string odds = tokenizer.Odds;
  477. tokenizer.Verbatim = true;
  478. inside_tags = GetVerbatim (tokenizer.get_token (), "%>");
  479. if (databinding && odds != null && odds.Length > 0) {
  480. databinding = false;
  481. // We encountered <% #something here %>, this should be passed
  482. // verbatim to the compiler
  483. inside_tags = '#' + inside_tags;
  484. }
  485. tokenizer.Verbatim = false;
  486. id = inside_tags;
  487. attributes = null;
  488. tagtype = (databinding ? TagType.DataBinding :
  489. (varname ? TagType.CodeRenderExpression : TagType.CodeRender));
  490. }
  491. public override string ToString ()
  492. {
  493. StringBuilder sb = new StringBuilder ("AspParser {");
  494. if (filename != null && filename.Length > 0)
  495. sb.AppendFormat ("{0}:{1}.{2}", filename, beginLine, beginColumn);
  496. sb.Append ('}');
  497. return sb.ToString ();
  498. }
  499. void OnError (string msg)
  500. {
  501. ParseErrorHandler eh = events [errorEvent] as ParseErrorHandler;
  502. if (eh != null)
  503. eh (this, msg);
  504. }
  505. void OnTagParsed (TagType tagtype, string id, TagAttributes attributes)
  506. {
  507. TagParsedHandler eh = events [tagParsedEvent] as TagParsedHandler;
  508. if (eh != null)
  509. eh (this, tagtype, id, attributes);
  510. }
  511. void OnTextParsed (string text)
  512. {
  513. TextParsedHandler eh = events [textParsedEvent] as TextParsedHandler;
  514. if (eh != null)
  515. eh (this, text);
  516. }
  517. #if NET_2_0
  518. void OnParsingComplete ()
  519. {
  520. ParsingCompleteHandler eh = events [parsingCompleteEvent] as ParsingCompleteHandler;
  521. if (eh != null)
  522. eh ();
  523. }
  524. #endif
  525. }
  526. }