AspParser.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  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. delegate void ParsingCompleteHandler ();
  45. class AspParser : ILocation
  46. {
  47. static readonly object errorEvent = new object ();
  48. static readonly object tagParsedEvent = new object ();
  49. static readonly object textParsedEvent = new object ();
  50. static readonly object parsingCompleteEvent = new object();
  51. MD5 checksum;
  52. AspTokenizer tokenizer;
  53. int beginLine, endLine;
  54. int beginColumn, endColumn;
  55. int beginPosition, endPosition;
  56. string filename;
  57. string verbatimID;
  58. string fileText;
  59. StringReader fileReader;
  60. bool _internal;
  61. int _internalLineOffset;
  62. int _internalPositionOffset;
  63. AspParser outer;
  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. public event ParsingCompleteHandler ParsingComplete {
  78. add { events.AddHandler (parsingCompleteEvent, value); }
  79. remove { events.RemoveHandler (parsingCompleteEvent, value); }
  80. }
  81. public AspParser (string filename, TextReader input)
  82. {
  83. this.filename = filename;
  84. this.fileText = input.ReadToEnd ();
  85. this.fileReader = new StringReader (this.fileText);
  86. this._internalLineOffset = 0;
  87. tokenizer = new AspTokenizer (this.fileReader);
  88. }
  89. public AspParser (string filename, TextReader input, int startLineOffset, int positionOffset, AspParser outer)
  90. : this (filename, input)
  91. {
  92. this._internal = true;
  93. this._internalLineOffset = startLineOffset;
  94. this._internalPositionOffset = positionOffset;
  95. this.outer = outer;
  96. }
  97. public byte[] MD5Checksum {
  98. get {
  99. if (checksum == null)
  100. return new byte[0];
  101. return checksum.Hash;
  102. }
  103. }
  104. public int BeginPosition {
  105. get { return beginPosition; }
  106. }
  107. public int EndPosition {
  108. get { return endPosition; }
  109. }
  110. public int BeginLine {
  111. get {
  112. if (_internal)
  113. return beginLine + _internalLineOffset;
  114. return beginLine;
  115. }
  116. }
  117. public int BeginColumn {
  118. get { return beginColumn; }
  119. }
  120. public int EndLine {
  121. get {
  122. if (_internal)
  123. return endLine + _internalLineOffset;
  124. return endLine;
  125. }
  126. }
  127. public int EndColumn {
  128. get { return endColumn; }
  129. }
  130. public string FileText {
  131. get {
  132. string ret = null;
  133. if (_internal && outer != null)
  134. ret = outer.FileText;
  135. if (ret == null && fileText != null)
  136. ret = fileText;
  137. return ret;
  138. }
  139. }
  140. public string PlainText {
  141. get {
  142. if (beginPosition >= endPosition || fileText == null)
  143. return null;
  144. string text = FileText;
  145. int start, len;
  146. if (_internal && outer != null) {
  147. start = beginPosition + _internalPositionOffset;
  148. len = (endPosition + _internalPositionOffset) - start;
  149. } else {
  150. start = beginPosition;
  151. len = endPosition - beginPosition;
  152. }
  153. if (text != null)
  154. return text.Substring (start, len);
  155. return null;
  156. }
  157. }
  158. public string Filename {
  159. get {
  160. if (_internal && outer != null)
  161. return outer.Filename;
  162. return filename;
  163. }
  164. }
  165. public string VerbatimID {
  166. set {
  167. tokenizer.Verbatim = true;
  168. verbatimID = value;
  169. }
  170. }
  171. bool Eat (int expected_token)
  172. {
  173. int token = tokenizer.get_token ();
  174. if (token != expected_token) {
  175. tokenizer.put_back ();
  176. return false;
  177. }
  178. endLine = tokenizer.EndLine;
  179. endColumn = tokenizer.EndColumn;
  180. return true;
  181. }
  182. void BeginElement ()
  183. {
  184. beginLine = tokenizer.BeginLine;
  185. beginColumn = tokenizer.BeginColumn;
  186. beginPosition = tokenizer.Position - 1;
  187. }
  188. void EndElement ()
  189. {
  190. endLine = tokenizer.EndLine;
  191. endColumn = tokenizer.EndColumn;
  192. endPosition = tokenizer.Position;
  193. }
  194. public void Parse ()
  195. {
  196. if (tokenizer == null) {
  197. OnError ("AspParser not initialized properly.");
  198. return;
  199. }
  200. int token;
  201. string id;
  202. TagAttributes attributes;
  203. TagType tagtype = TagType.Text;
  204. StringBuilder text = new StringBuilder ();
  205. try {
  206. while ((token = tokenizer.get_token ()) != Token.EOF) {
  207. BeginElement ();
  208. if (tokenizer.Verbatim){
  209. string end_verbatim = "</" + verbatimID + ">";
  210. string verbatim_text = GetVerbatim (token, end_verbatim);
  211. if (verbatim_text == null)
  212. OnError ("Unexpected EOF processing " + verbatimID);
  213. tokenizer.Verbatim = false;
  214. EndElement ();
  215. endPosition -= end_verbatim.Length;
  216. OnTextParsed (verbatim_text);
  217. beginPosition = endPosition;
  218. endPosition += end_verbatim.Length;
  219. OnTagParsed (TagType.Close, verbatimID, null);
  220. continue;
  221. }
  222. if (token == '<') {
  223. GetTag (out tagtype, out id, out attributes);
  224. EndElement ();
  225. if (tagtype == TagType.ServerComment)
  226. continue;
  227. if (tagtype == TagType.Text)
  228. OnTextParsed (id);
  229. else
  230. OnTagParsed (tagtype, id, attributes);
  231. continue;
  232. }
  233. if (tokenizer.Value.Trim ().Length == 0 && tagtype == TagType.Directive) {
  234. continue;
  235. }
  236. text.Length = 0;
  237. do {
  238. text.Append (tokenizer.Value);
  239. token = tokenizer.get_token ();
  240. } while (token != '<' && token != Token.EOF);
  241. tokenizer.put_back ();
  242. EndElement ();
  243. OnTextParsed (text.ToString ());
  244. }
  245. } finally {
  246. if (fileReader != null) {
  247. fileReader.Close ();
  248. fileReader = null;
  249. }
  250. checksum = tokenizer.Checksum;
  251. tokenizer = null;
  252. }
  253. OnParsingComplete ();
  254. }
  255. bool GetInclude (string str, out string pathType, out string filename)
  256. {
  257. pathType = null;
  258. filename = null;
  259. str = str.Substring (2).Trim ();
  260. int len = str.Length;
  261. int lastQuote = str.LastIndexOf ('"');
  262. if (len < 10 || lastQuote != len - 1)
  263. return false;
  264. if (!StrUtils.StartsWith (str, "#include ", true))
  265. return false;
  266. str = str.Substring (9).Trim ();
  267. bool isfile = (StrUtils.StartsWith (str ,"file", true));
  268. if (!isfile && !StrUtils.StartsWith (str, "virtual", true))
  269. return false;
  270. pathType = (isfile) ? "file" : "virtual";
  271. if (str.Length < pathType.Length + 3)
  272. return false;
  273. str = str.Substring (pathType.Length).Trim ();
  274. if (str.Length < 3 || str [0] != '=')
  275. return false;
  276. int index = 1;
  277. for (; index < str.Length; index++) {
  278. if (Char.IsWhiteSpace (str [index]))
  279. continue;
  280. else if (str [index] == '"')
  281. break;
  282. }
  283. if (index == str.Length || index == lastQuote)
  284. return false;
  285. str = str.Substring (index);
  286. if (str.Length == 2) { // only quotes
  287. OnError ("Empty file name.");
  288. return false;
  289. }
  290. filename = str.Trim ().Substring (index, str.Length - 2);
  291. if (filename.LastIndexOf ('"') != -1)
  292. return false; // file=""" -> no error
  293. return true;
  294. }
  295. void GetTag (out TagType tagtype, out string id, out TagAttributes attributes)
  296. {
  297. int token = tokenizer.get_token ();
  298. tagtype = TagType.ServerComment;
  299. id = null;
  300. attributes = null;
  301. switch (token){
  302. case '%':
  303. GetServerTag (out tagtype, out id, out attributes);
  304. break;
  305. case '/':
  306. if (!Eat (Token.IDENTIFIER))
  307. OnError ("expecting TAGNAME");
  308. id = tokenizer.Value;
  309. if (!Eat ('>'))
  310. OnError ("expecting '>'. Got '" + id + "'");
  311. tagtype = TagType.Close;
  312. break;
  313. case '!':
  314. bool double_dash = Eat (Token.DOUBLEDASH);
  315. if (double_dash)
  316. tokenizer.put_back ();
  317. tokenizer.Verbatim = true;
  318. string end = double_dash ? "-->" : ">";
  319. string comment = GetVerbatim (tokenizer.get_token (), end);
  320. tokenizer.Verbatim = false;
  321. if (comment == null)
  322. OnError ("Unfinished HTML comment/DTD");
  323. string pathType, filename;
  324. if (double_dash && GetInclude (comment, out pathType, out filename)) {
  325. tagtype = TagType.Include;
  326. attributes = new TagAttributes ();
  327. attributes.Add (pathType, filename);
  328. } else {
  329. tagtype = TagType.Text;
  330. id = "<!" + comment + end;
  331. }
  332. break;
  333. case Token.IDENTIFIER:
  334. if (this.filename == "@@inner_string@@") {
  335. // Actually not tag but "xxx < yyy" stuff in inner_string!
  336. tagtype = TagType.Text;
  337. tokenizer.InTag = false;
  338. id = "<" + tokenizer.Odds + tokenizer.Value;
  339. } else {
  340. id = tokenizer.Value;
  341. try {
  342. attributes = GetAttributes ();
  343. } catch (Exception e) {
  344. OnError (e.Message);
  345. break;
  346. }
  347. tagtype = TagType.Tag;
  348. if (Eat ('/') && Eat ('>')) {
  349. tagtype = TagType.SelfClosing;
  350. } else if (!Eat ('>')) {
  351. if (attributes.IsRunAtServer ()) {
  352. OnError ("The server tag is not well formed.");
  353. break;
  354. }
  355. tokenizer.Verbatim = true;
  356. attributes.Add (String.Empty, GetVerbatim (tokenizer.get_token (), ">") + ">");
  357. tokenizer.Verbatim = false;
  358. }
  359. }
  360. break;
  361. default:
  362. string idvalue = null;
  363. // This is to handle code like:
  364. //
  365. // <asp:ListItem runat="server"> < </asp:ListItem>
  366. //
  367. if ((char)token == '<') {
  368. string odds = tokenizer.Odds;
  369. if (odds != null && odds.Length > 0 && Char.IsWhiteSpace (odds [0])) {
  370. tokenizer.put_back ();
  371. idvalue = odds;
  372. } else
  373. idvalue = tokenizer.Value;
  374. } else
  375. idvalue = tokenizer.Value;
  376. tagtype = TagType.Text;
  377. tokenizer.InTag = false;
  378. id = "<" + idvalue;
  379. break;
  380. }
  381. }
  382. TagAttributes GetAttributes ()
  383. {
  384. int token;
  385. TagAttributes attributes;
  386. string id;
  387. bool wellFormedForServer = true;
  388. attributes = new TagAttributes ();
  389. while ((token = tokenizer.get_token ()) != Token.EOF){
  390. if (token == '<' && Eat ('%')) {
  391. tokenizer.Verbatim = true;
  392. attributes.Add (String.Empty, "<%" +
  393. GetVerbatim (tokenizer.get_token (), "%>") + "%>");
  394. tokenizer.Verbatim = false;
  395. tokenizer.InTag = true;
  396. continue;
  397. }
  398. if (token != Token.IDENTIFIER)
  399. break;
  400. id = tokenizer.Value;
  401. if (Eat ('=')){
  402. if (Eat (Token.ATTVALUE)){
  403. attributes.Add (id, tokenizer.Value);
  404. wellFormedForServer &= tokenizer.AlternatingQuotes;
  405. } else if (Eat ('<') && Eat ('%')) {
  406. tokenizer.Verbatim = true;
  407. attributes.Add (id, "<%" +
  408. GetVerbatim (tokenizer.get_token (), "%>") + "%>");
  409. tokenizer.Verbatim = false;
  410. tokenizer.InTag = true;
  411. } else {
  412. OnError ("expected ATTVALUE");
  413. return null;
  414. }
  415. } else {
  416. attributes.Add (id, null);
  417. }
  418. }
  419. tokenizer.put_back ();
  420. if (attributes.IsRunAtServer () && !wellFormedForServer) {
  421. OnError ("The server tag is not well formed.");
  422. return null;
  423. }
  424. return attributes;
  425. }
  426. string GetVerbatim (int token, string end)
  427. {
  428. StringBuilder vb_text = new StringBuilder ();
  429. StringBuilder tmp = new StringBuilder ();
  430. int i = 0;
  431. if (tokenizer.Value.Length > 1){
  432. // May be we have a put_back token that is not a single character
  433. vb_text.Append (tokenizer.Value);
  434. token = tokenizer.get_token ();
  435. }
  436. end = end.ToLower (Helpers.InvariantCulture);
  437. int repeated = 0;
  438. for (int k = 0; k < end.Length; k++)
  439. if (end [0] == end [k])
  440. repeated++;
  441. while (token != Token.EOF){
  442. if (Char.ToLower ((char) token, Helpers.InvariantCulture) == end [i]){
  443. if (++i >= end.Length)
  444. break;
  445. tmp.Append ((char) token);
  446. token = tokenizer.get_token ();
  447. continue;
  448. } else if (i > 0) {
  449. if (repeated > 1 && i == repeated && (char) token == end [0]) {
  450. vb_text.Append ((char) token);
  451. token = tokenizer.get_token ();
  452. continue;
  453. }
  454. vb_text.Append (tmp.ToString ());
  455. tmp.Remove (0, tmp.Length);
  456. i = 0;
  457. }
  458. vb_text.Append ((char) token);
  459. token = tokenizer.get_token ();
  460. }
  461. if (token == Token.EOF)
  462. OnError ("Expecting " + end + " and got EOF.");
  463. return RemoveComments (vb_text.ToString ());
  464. }
  465. string RemoveComments (string text)
  466. {
  467. int end;
  468. int start = text.IndexOf ("<%--");
  469. while (start != -1) {
  470. end = text.IndexOf ("--%>");
  471. if (end == -1 || end <= start + 1)
  472. break;
  473. text = text.Remove (start, end - start + 4);
  474. start = text.IndexOf ("<%--");
  475. }
  476. return text;
  477. }
  478. void GetServerTag (out TagType tagtype, out string id, out TagAttributes attributes)
  479. {
  480. string inside_tags;
  481. bool old = tokenizer.ExpectAttrValue;
  482. tokenizer.ExpectAttrValue = false;
  483. if (Eat ('@')){
  484. tokenizer.ExpectAttrValue = old;
  485. tagtype = TagType.Directive;
  486. id = "";
  487. if (Eat (Token.DIRECTIVE))
  488. id = tokenizer.Value;
  489. attributes = GetAttributes ();
  490. if (!Eat ('%') || !Eat ('>'))
  491. OnError ("expecting '%>'");
  492. return;
  493. }
  494. if (Eat (Token.DOUBLEDASH)) {
  495. tokenizer.ExpectAttrValue = old;
  496. tokenizer.Verbatim = true;
  497. inside_tags = GetVerbatim (tokenizer.get_token (), "--%>");
  498. tokenizer.Verbatim = false;
  499. id = null;
  500. attributes = null;
  501. tagtype = TagType.ServerComment;
  502. return;
  503. }
  504. tokenizer.ExpectAttrValue = old;
  505. bool varname;
  506. bool databinding;
  507. #if NET_4_0
  508. bool codeRenderEncode;
  509. #endif
  510. varname = Eat ('=');
  511. databinding = !varname && Eat ('#');
  512. #if NET_4_0
  513. codeRenderEncode = !databinding && !varname && Eat (':');
  514. #endif
  515. string odds = tokenizer.Odds;
  516. tokenizer.Verbatim = true;
  517. inside_tags = GetVerbatim (tokenizer.get_token (), "%>");
  518. if (databinding && odds != null && odds.Length > 0) {
  519. databinding = false;
  520. // We encountered <% #something here %>, this should be passed
  521. // verbatim to the compiler
  522. inside_tags = '#' + inside_tags;
  523. }
  524. tokenizer.Verbatim = false;
  525. id = inside_tags;
  526. attributes = null;
  527. if (databinding)
  528. tagtype = TagType.DataBinding;
  529. else if (varname)
  530. tagtype = TagType.CodeRenderExpression;
  531. #if NET_4_0
  532. else if (codeRenderEncode)
  533. tagtype = TagType.CodeRenderEncode;
  534. #endif
  535. else
  536. tagtype = TagType.CodeRender;
  537. }
  538. public override string ToString ()
  539. {
  540. StringBuilder sb = new StringBuilder ("AspParser {");
  541. if (filename != null && filename.Length > 0)
  542. sb.AppendFormat ("{0}:{1}.{2}", filename, beginLine, beginColumn);
  543. sb.Append ('}');
  544. return sb.ToString ();
  545. }
  546. void OnError (string msg)
  547. {
  548. ParseErrorHandler eh = events [errorEvent] as ParseErrorHandler;
  549. if (eh != null)
  550. eh (this, msg);
  551. }
  552. void OnTagParsed (TagType tagtype, string id, TagAttributes attributes)
  553. {
  554. TagParsedHandler eh = events [tagParsedEvent] as TagParsedHandler;
  555. if (eh != null)
  556. eh (this, tagtype, id, attributes);
  557. }
  558. void OnTextParsed (string text)
  559. {
  560. TextParsedHandler eh = events [textParsedEvent] as TextParsedHandler;
  561. if (eh != null)
  562. eh (this, text);
  563. }
  564. void OnParsingComplete ()
  565. {
  566. ParsingCompleteHandler eh = events [parsingCompleteEvent] as ParsingCompleteHandler;
  567. if (eh != null)
  568. eh ();
  569. }
  570. }
  571. }