c99f839de2d80da3c50737dec3fedb6b0495f3d7
[mono.git] / mcs / class / System.Web / System.Web.UI / TemplateParser.cs
1 //
2 // System.Web.UI.TemplateParser
3 //
4 // Authors:
5 //      Duncan Mak (duncan@ximian.com)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (C) 2002,2003 Ximian, Inc. (http://www.ximian.com)
9 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
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
31 using System.CodeDom.Compiler;
32 using System.Collections;
33 using System.IO;
34 using System.Reflection;
35 using System.Security.Permissions;
36 using System.Web.Compilation;
37 using System.Web.Configuration;
38 using System.Web.Util;
39
40 namespace System.Web.UI {
41
42         // CAS
43         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
44         [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
45         public abstract class TemplateParser : BaseParser
46         {
47                 string inputFile;
48                 string text;
49                 string privateBinPath;
50                 Hashtable mainAttributes;
51                 ArrayList dependencies;
52                 ArrayList assemblies;
53                 Hashtable anames;
54                 ArrayList imports;
55                 ArrayList interfaces;
56                 ArrayList scripts;
57                 Type baseType;
58                 string className;
59                 RootBuilder rootBuilder;
60                 bool debug;
61                 string compilerOptions;
62                 string language;
63                 bool strictOn = false;
64                 bool explicitOn = false;
65                 bool output_cache;
66                 int oc_duration;
67                 string oc_header, oc_custom, oc_param, oc_controls;
68                 bool oc_shared;
69                 OutputCacheLocation oc_location;
70 #if NET_2_0
71                 string src;
72                 string partialClassName;
73 #endif
74                 Assembly srcAssembly;
75                 int appAssemblyIndex = -1;
76
77                 internal TemplateParser ()
78                 {
79                         imports = new ArrayList ();
80                         imports.Add ("System");
81                         imports.Add ("System.Collections");
82                         imports.Add ("System.Collections.Specialized");
83                         imports.Add ("System.Configuration");
84                         imports.Add ("System.Text");
85                         imports.Add ("System.Text.RegularExpressions");
86                         imports.Add ("System.Web");
87                         imports.Add ("System.Web.Caching");
88                         imports.Add ("System.Web.Security");
89                         imports.Add ("System.Web.SessionState");
90                         imports.Add ("System.Web.UI");
91                         imports.Add ("System.Web.UI.WebControls");
92                         imports.Add ("System.Web.UI.HtmlControls");
93
94                         assemblies = new ArrayList ();
95 #if NET_2_0
96                         bool addAssembliesInBin = false;
97                         foreach (AssemblyInfo info in CompilationConfig.Assemblies) {
98                                 if (info.Assembly == "*")
99                                         addAssembliesInBin = true;
100                                 else
101                                         AddAssemblyByName (info.Assembly);
102                         }
103                         if (addAssembliesInBin)
104                                 AddAssembliesInBin ();
105
106                         foreach (NamespaceInfo info in PagesConfig.Namespaces) {
107                                 imports.Add (info.Namespace);
108                         }
109 #else
110                         foreach (string a in CompilationConfig.Assemblies)
111                                 AddAssemblyByName (a);
112                         if (CompilationConfig.AssembliesInBin)
113                                 AddAssembliesInBin ();
114 #endif
115
116                         language = CompilationConfig.DefaultLanguage;
117                 }
118
119                 internal void AddApplicationAssembly ()
120                 {
121                         string location = Context.ApplicationInstance.AssemblyLocation;
122                         if (location != typeof (TemplateParser).Assembly.Location) {
123                                 appAssemblyIndex = assemblies.Add (location);
124                         }
125                 }
126
127                 protected abstract Type CompileIntoType ();
128
129                 internal virtual void HandleOptions (object obj)
130                 {
131                 }
132
133                 internal static string GetOneKey (Hashtable tbl)
134                 {
135                         foreach (object key in tbl.Keys)
136                                 return key.ToString ();
137
138                         return null;
139                 }
140                 
141                 internal virtual void AddDirective (string directive, Hashtable atts)
142                 {
143                         if (String.Compare (directive, DefaultDirectiveName, true) == 0) {
144                                 if (mainAttributes != null)
145                                         ThrowParseException ("Only 1 " + DefaultDirectiveName + " is allowed");
146
147                                 mainAttributes = atts;
148                                 ProcessMainAttributes (mainAttributes);
149                                 return;
150                         }
151
152                         int cmp = String.Compare ("Assembly", directive, true);
153                         if (cmp == 0) {
154                                 string name = GetString (atts, "Name", null);
155                                 string src = GetString (atts, "Src", null);
156
157                                 if (atts.Count > 0)
158                                         ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
159
160                                 if (name == null && src == null)
161                                         ThrowParseException ("You gotta specify Src or Name");
162                                         
163                                 if (name != null && src != null)
164                                         ThrowParseException ("Src and Name cannot be used together");
165
166                                 if (name != null) {
167                                         AddAssemblyByName (name);
168                                 } else {
169                                         GetAssemblyFromSource (src);
170                                 }
171
172                                 return;
173                         }
174
175                         cmp = String.Compare ("Import", directive, true);
176                         if (cmp == 0) {
177                                 string namesp = GetString (atts, "Namespace", null);
178                                 if (atts.Count > 0)
179                                         ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
180                                 
181                                 if (namesp != null && namesp != "")
182                                         AddImport (namesp);
183                                 return;
184                         }
185
186                         cmp = String.Compare ("Implements", directive, true);
187                         if (cmp == 0) {
188                                 string ifacename = GetString (atts, "Interface", "");
189
190                                 if (atts.Count > 0)
191                                         ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
192                                 
193                                 Type iface = LoadType (ifacename);
194                                 if (iface == null)
195                                         ThrowParseException ("Cannot find type " + ifacename);
196
197                                 if (!iface.IsInterface)
198                                         ThrowParseException (iface + " is not an interface");
199
200                                 AddInterface (iface.FullName);
201                                 return;
202                         }
203
204                         cmp = String.Compare ("OutputCache", directive, true);
205                         if (cmp == 0) {
206                                 output_cache = true;
207                                 
208                                 if (atts ["Duration"] == null)
209                                         ThrowParseException ("The directive is missing a 'duration' attribute.");
210                                 if (atts ["VaryByParam"] == null)
211                                         ThrowParseException ("This directive is missing a 'VaryByParam' " +
212                                                         "attribute, which should be set to \"none\", \"*\", " +
213                                                         "or a list of name/value pairs.");
214
215                                 foreach (DictionaryEntry entry in atts) {
216                                         string key = (string) entry.Key;
217                                         switch (key.ToLower ()) {
218                                         case "duration":
219                                                 oc_duration = Int32.Parse ((string) entry.Value);
220                                                 if (oc_duration < 1)
221                                                         ThrowParseException ("The 'duration' attribute must be set " +
222                                                                         "to a positive integer value");
223                                                 break;
224                                         case "varybyparam":
225                                                 oc_param = (string) entry.Value;
226                                                 if (String.Compare (oc_param, "none") == 0)
227                                                         oc_param = null;
228                                                 break;
229                                         case "varybyheader":
230                                                 oc_header = (string) entry.Value;
231                                                 break;
232                                         case "varybycustom":
233                                                 oc_custom = (string) entry.Value;
234                                                 break;
235                                         case "location":
236                                                 if (!(this is PageParser))
237                                                         goto default;
238
239                                                 try {
240                                                         oc_location = (OutputCacheLocation) Enum.Parse (
241                                                                 typeof (OutputCacheLocation), (string) entry.Value, true);
242                                                 } catch {
243                                                         ThrowParseException ("The 'location' attribute is case sensitive and " +
244                                                                         "must be one of the following values: Any, Client, " +
245                                                                         "Downstream, Server, None, ServerAndClient.");
246                                                 }
247                                                 break;
248                                         case "varybycontrol":
249                                                 if (this is PageParser)
250                                                         goto default;
251
252                                                 oc_controls = (string) entry.Value;
253                                                 break;
254                                         case "shared":
255                                                 if (this is PageParser)
256                                                         goto default;
257
258                                                 try {
259                                                         oc_shared = Boolean.Parse ((string) entry.Value);
260                                                 } catch {
261                                                         ThrowParseException ("The 'shared' attribute is case sensitive" +
262                                                                         " and must be set to 'true' or 'false'.");
263                                                 }
264                                                 break;
265                                         default:
266                                                 ThrowParseException ("The '" + key + "' attribute is not " +
267                                                                 "supported by the 'Outputcache' directive.");
268                                                 break;
269                                         }
270                                         
271                                 }
272                                 
273                                 return;
274                         }
275
276                         ThrowParseException ("Unknown directive: " + directive);
277                 }
278
279                 internal Type LoadType (string typeName)
280                 {
281                         // First try loaded assemblies, then try assemblies in Bin directory.
282                         Type type = null;
283                         bool seenBin = false;
284                         Assembly [] assemblies = AppDomain.CurrentDomain.GetAssemblies ();
285                         foreach (Assembly ass in assemblies) {
286                                 type = ass.GetType (typeName);
287                                 if (type == null)
288                                         continue;
289
290                                 if (Path.GetDirectoryName (ass.Location) != PrivateBinPath) {
291                                         AddAssembly (ass, true);
292                                 } else {
293                                         seenBin = true;
294                                 }
295
296                                 AddDependency (ass.Location);
297                                 return type;
298                         }
299
300                         if (seenBin)
301                                 return null;
302
303                         // Load from bin
304                         if (!Directory.Exists (PrivateBinPath))
305                                 return null;
306
307                         string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
308                         foreach (string s in binDlls) {
309                                 Assembly binA = Assembly.LoadFrom (s);
310                                 type = binA.GetType (typeName);
311                                 if (type == null)
312                                         continue;
313
314                                 AddDependency (binA.Location);
315                                 return type;
316                         }
317
318                         return null;
319                 }
320
321                 void AddAssembliesInBin ()
322                 {
323                         if (!Directory.Exists (PrivateBinPath))
324                                 return;
325
326                         string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
327                         foreach (string s in binDlls) {
328                                 assemblies.Add (s);
329                         }
330                 }
331
332                 internal virtual void AddInterface (string iface)
333                 {
334                         if (interfaces == null)
335                                 interfaces = new ArrayList ();
336
337                         if (!interfaces.Contains (iface))
338                                 interfaces.Add (iface);
339                 }
340                 
341                 internal virtual void AddImport (string namesp)
342                 {
343                         if (imports == null)
344                                 imports = new ArrayList ();
345
346                         if (!imports.Contains (namesp))
347                                 imports.Add (namesp);
348                 }
349
350                 internal virtual void AddSourceDependency (string filename)
351                 {
352                         if (dependencies != null && dependencies.Contains (filename)) {
353                                 ThrowParseException ("Circular file references are not allowed. File: " + filename);
354                         }
355
356                         AddDependency (filename);
357                 }
358
359                 internal virtual void AddDependency (string filename)
360                 {
361                         if (filename == "")
362                                 return;
363
364                         if (dependencies == null)
365                                 dependencies = new ArrayList ();
366
367                         if (!dependencies.Contains (filename))
368                                 dependencies.Add (filename);
369                 }
370                 
371                 internal virtual void AddAssembly (Assembly assembly, bool fullPath)
372                 {
373                         if (assembly.Location == "")
374                                 return;
375
376                         if (anames == null)
377                                 anames = new Hashtable ();
378
379                         string name = assembly.GetName ().Name;
380                         string loc = assembly.Location;
381                         if (fullPath) {
382                                 if (!assemblies.Contains (loc)) {
383                                         assemblies.Add (loc);
384                                 }
385
386                                 anames [name] = loc;
387                                 anames [loc] = assembly;
388                         } else {
389                                 if (!assemblies.Contains (name)) {
390                                         assemblies.Add (name);
391                                 }
392
393                                 anames [name] = assembly;
394                         }
395                 }
396
397                 internal virtual Assembly AddAssemblyByFileName (string filename)
398                 {
399                         Assembly assembly = null;
400                         Exception error = null;
401
402                         try {
403                                 assembly = Assembly.LoadFrom (filename);
404                         } catch (Exception e) { error = e; }
405
406                         if (assembly == null)
407                                 ThrowParseException ("Assembly " + filename + " not found", error);
408
409                         AddAssembly (assembly, true);
410                         return assembly;
411                 }
412
413                 internal virtual Assembly AddAssemblyByName (string name)
414                 {
415                         if (anames == null)
416                                 anames = new Hashtable ();
417
418                         if (anames.Contains (name)) {
419                                 object o = anames [name];
420                                 if (o is string)
421                                         o = anames [o];
422
423                                 return (Assembly) o;
424                         }
425
426                         Assembly assembly = null;
427                         Exception error = null;
428                         if (name.IndexOf (',') != -1) {
429                                 try {
430                                         assembly = Assembly.Load (name);
431                                 } catch (Exception e) { error = e; }
432                         }
433
434                         if (assembly == null) {
435                                 try {
436                                         assembly = Assembly.LoadWithPartialName (name);
437                                 } catch (Exception e) { error = e; }
438                         }
439                         
440                         if (assembly == null)
441                                 ThrowParseException ("Assembly " + name + " not found", error);
442
443                         AddAssembly (assembly, true);
444                         return assembly;
445                 }
446
447                 internal virtual void ProcessMainAttributes (Hashtable atts)
448                 {
449                         atts.Remove ("Description"); // ignored
450 #if NET_1_1
451                         atts.Remove ("CodeBehind");  // ignored
452 #endif
453                         atts.Remove ("AspCompat"); // ignored
454
455                         debug = GetBool (atts, "Debug", true);
456                         compilerOptions = GetString (atts, "CompilerOptions", "");
457                         language = GetString (atts, "Language", CompilationConfig.DefaultLanguage);
458                         strictOn = GetBool (atts, "Strict", CompilationConfig.Strict);
459                         explicitOn = GetBool (atts, "Explicit", CompilationConfig.Explicit);
460
461                         string inherits = GetString (atts, "Inherits", null);
462 #if NET_2_0
463                         // In ASP 2, the source file is actually integrated with
464                         // the generated file via the use of partial classes. This
465                         // means that the code file has to be confirmed, but not
466                         // used at this point.
467                         src = GetString (atts, "CodeFile", null);
468
469                         if (src != null && inherits != null) {
470                                 // Make sure the source exists
471                                 src = UrlUtils.Combine (BaseVirtualDir, src);
472                                 string realPath = MapPath (src, false);
473                                 if (!File.Exists (realPath))
474                                         ThrowParseException ("File " + src + " not found");
475
476                                 // Verify that the inherits is a valid identify not a
477                                 // fully-qualified name.
478                                 if (!CodeGenerator.IsValidLanguageIndependentIdentifier (inherits))
479                                         ThrowParseException (String.Format ("'{0}' is not valid for 'inherits'", inherits));
480
481                                 // We are going to create a partial class that shares
482                                 // the same name as the inherits tag, so reset the
483                                 // name. The base type is changed because it is the
484                                 // code file's responsibilty to extend the classes
485                                 // needed.
486                                 partialClassName = inherits;
487
488                                 // Add the code file as an option to the
489                                 // compiler. This lets both files be compiled at once.
490                                 compilerOptions += " \"" + realPath + "\"";
491                         } else if (inherits != null) {
492                                 // We just set the inherits directly because this is a
493                                 // Single-Page model.
494                                 SetBaseType (inherits);
495                         }
496 #else
497                         string src = GetString (atts, "Src", null);
498
499                         if (src != null)
500                                 srcAssembly = GetAssemblyFromSource (src);
501
502                         if (inherits != null)
503                                 SetBaseType (inherits);
504
505                         className = GetString (atts, "ClassName", null);
506                         if (className != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (className))
507                                 ThrowParseException (String.Format ("'{0}' is not valid for 'className'", className));
508 #endif
509
510                         if (atts.Count > 0)
511                                 ThrowParseException ("Unknown attribute: " + GetOneKey (atts));
512                 }
513
514                 internal void SetBaseType (string type)
515                 {
516                         if (type == DefaultBaseTypeName)
517                                 return;
518
519                         Type parent = null;
520                         if (srcAssembly != null)
521                                 parent = srcAssembly.GetType (type);
522
523                         if (parent == null)
524                                 parent = LoadType (type);
525
526                         if (parent == null)
527                                 ThrowParseException ("Cannot find type " + type);
528
529                         if (!DefaultBaseType.IsAssignableFrom (parent))
530                                 ThrowParseException ("The parent type does not derive from " + DefaultBaseType);
531
532                         baseType = parent;
533                 }
534
535                 Assembly GetAssemblyFromSource (string vpath)
536                 {
537                         vpath = UrlUtils.Combine (BaseVirtualDir, vpath);
538                         string realPath = MapPath (vpath, false);
539                         if (!File.Exists (realPath))
540                                 ThrowParseException ("File " + vpath + " not found");
541
542                         AddSourceDependency (realPath);
543
544                         CompilerResults result = CachingCompiler.Compile (language, realPath, realPath, assemblies);
545                         if (result.NativeCompilerReturnValue != 0) {
546                                 StreamReader reader = new StreamReader (realPath);
547                                 throw new CompilationException (realPath, result.Errors, reader.ReadToEnd ());
548                         }
549
550                         AddAssembly (result.CompiledAssembly, true);
551                         return result.CompiledAssembly;
552                 }
553                 
554                 internal abstract Type DefaultBaseType { get; }
555                 internal abstract string DefaultBaseTypeName { get; }
556                 internal abstract string DefaultDirectiveName { get; }
557
558                 internal string InputFile
559                 {
560                         get { return inputFile; }
561                         set { inputFile = value; }
562                 }
563
564 #if NET_2_0
565                 internal bool IsPartial
566                 {
567                         get { return src != null; }
568                 }
569
570                 internal string PartialClassName
571                 {
572                         get { return partialClassName; }
573                 }
574 #endif
575
576                 internal string Text
577                 {
578                         get { return text; }
579                         set { text = value; }
580                 }
581
582                 internal Type BaseType
583                 {
584                         get {
585                                 if (baseType == null)
586                                         baseType = DefaultBaseType;
587
588                                 return baseType;
589                         }
590                 }
591                 
592                 internal string ClassName {
593                         get {
594                                 if (className != null)
595                                         return className;
596
597                                 className = Path.GetFileName (inputFile).Replace ('.', '_');
598                                 className = className.Replace ('-', '_'); 
599                                 className = className.Replace (' ', '_');
600
601                                 if (Char.IsDigit(className[0])) {
602                                         className = "_" + className;
603                                 }
604
605                                 return className;
606                         }
607                 }
608
609                 internal string PrivateBinPath {
610                         get {
611                                 if (privateBinPath != null)
612                                         return privateBinPath;
613
614                                 AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
615                                 privateBinPath = Path.Combine (setup.ApplicationBase, setup.PrivateBinPath);
616
617                                 return privateBinPath;
618                         }
619                 }
620
621                 internal ArrayList Scripts {
622                         get {
623                                 if (scripts == null)
624                                         scripts = new ArrayList ();
625
626                                 return scripts;
627                         }
628                 }
629
630                 internal ArrayList Imports {
631                         get { return imports; }
632                 }
633
634                 internal ArrayList Assemblies {
635                         get {
636                                 if (appAssemblyIndex != -1) {
637                                         object o = assemblies [appAssemblyIndex];
638                                         assemblies.RemoveAt (appAssemblyIndex);
639                                         assemblies.Add (o);
640                                         appAssemblyIndex = -1;
641                                 }
642
643                                 return assemblies;
644                         }
645                 }
646
647                 internal ArrayList Interfaces {
648                         get { return interfaces; }
649                 }
650
651                 internal RootBuilder RootBuilder {
652                         get { return rootBuilder; }
653                         set { rootBuilder = value; }
654                 }
655
656                 internal ArrayList Dependencies {
657                         get { return dependencies; }
658                         set { dependencies = value; }
659                 }
660
661                 internal string CompilerOptions {
662                         get { return compilerOptions; }
663                 }
664
665                 internal string Language {
666                         get { return language; }
667                 }
668
669                 internal bool StrictOn {
670                         get { return strictOn; }
671                 }
672
673                 internal bool ExplicitOn {
674                         get { return explicitOn; }
675                 }
676                 
677                 internal bool Debug {
678                         get { return debug; }
679                 }
680
681                 internal bool OutputCache {
682                         get { return output_cache; }
683                 }
684
685                 internal int OutputCacheDuration {
686                         get { return oc_duration; }
687                 }
688
689                 internal string OutputCacheVaryByHeader {
690                         get { return oc_header; }
691                 }
692
693                 internal string OutputCacheVaryByCustom {
694                         get { return oc_custom; }
695                 }
696
697                 internal string OutputCacheVaryByControls {
698                         get { return oc_controls; }
699                 }
700                 
701                 internal bool OutputCacheShared {
702                         get { return oc_shared; }
703                 }
704                 
705                 internal OutputCacheLocation OutputCacheLocation {
706                         get { return oc_location; }
707                 }
708
709                 internal string OutputCacheVaryByParam {
710                         get { return oc_param; }
711                 }
712
713 #if NET_2_0
714                 internal PagesSection PagesConfig {
715                         get {
716                                 return WebConfigurationManager.GetSection ("system.web/pages") as PagesSection;
717                         }
718                 }
719 #else
720                 internal PagesConfiguration PagesConfig {
721                         get { return PagesConfiguration.GetInstance (Context); }
722                 }
723 #endif
724         }
725 }
726