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