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