AspParser.cs 14 KB

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