2008-05-28 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.UI / SimpleWebHandlerParser.cs
1 //
2 // System.Web.UI.SimpleWebHandlerParser
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
8 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
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
30 using System.CodeDom.Compiler;
31 using System.Collections;
32 using System.IO;
33 using System.Reflection;
34 using System.Security.Permissions;
35 using System.Text;
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 SimpleWebHandlerParser
46         {
47                 HttpContext context;
48                 string vPath;
49                 string physPath;
50                 string className;
51                 bool debug;
52                 string language;
53                 string program;
54                 bool gotDefault;
55                 ArrayList assemblies;
56                 ArrayList dependencies;
57                 Hashtable anames;
58                 string privateBinPath;
59                 string baseDir;
60                 string baseVDir;
61 #if !NET_2_0
62                 CompilationConfiguration compilationConfig;
63 #endif
64                 TextReader reader;
65
66                 int appAssemblyIndex = -1;
67                 Type cachedType;
68
69                 protected SimpleWebHandlerParser (HttpContext context, string virtualPath, string physicalPath)
70                 : this (context, virtualPath, physicalPath, null)
71                 {}
72                 
73                 internal SimpleWebHandlerParser (HttpContext context, string virtualPath, string physicalPath, TextReader reader)
74                 {
75                         this.reader = reader;
76                         cachedType = CachingCompiler.GetTypeFromCache (physicalPath);
77                         if (cachedType != null)
78                                 return; // We don't need anything else.
79
80                         // context is obsolete in 2.0+ - MSDN recommends passing null, so we need to
81                         // take that into account
82                         if (context != null)
83                                 this.context = context;
84                         else
85                                 this.context = HttpContext.Current;
86                         
87                         this.vPath = virtualPath;
88                         AddDependency (virtualPath);
89                         
90                         // physicalPath is obsolete in 2.0+ - same note what for context applies here
91                         if (physicalPath != null && physicalPath.Length > 0)
92                                 this.physPath = physicalPath;
93                         else {
94                                 HttpRequest req = this.context != null ? context.Request : null;
95                                 if (req != null)
96                                         this.physPath = req.MapPath (virtualPath);
97                         }
98
99                         assemblies = new ArrayList ();
100                         string location = Context.ApplicationInstance.AssemblyLocation;
101                         if (location != typeof (TemplateParser).Assembly.Location)
102                                 appAssemblyIndex = assemblies.Add (location);
103
104 #if NET_2_0
105                         bool addAssembliesInBin = false;
106                         foreach (AssemblyInfo info in CompilationConfig.Assemblies) {
107                                 if (info.Assembly == "*")
108                                         addAssembliesInBin = true;
109                                 else
110                                         AddAssemblyByName (info.Assembly, null);
111                         }
112                         if (addAssembliesInBin)
113                                 AddAssembliesInBin ();
114 #else
115                         assemblies.AddRange (CompilationConfig.Assemblies);
116                         if (CompilationConfig.AssembliesInBin)
117                                 AddAssembliesInBin ();
118 #endif
119
120                         language = CompilationConfig.DefaultLanguage;
121
122                         GetDirectivesAndContent ();
123                 }
124
125                 protected Type GetCompiledTypeFromCache ()
126                 {
127                         return cachedType;
128                 }
129
130                 void GetDirectivesAndContent ()
131                 {
132                         string line;
133                         bool directiveFound = false;
134                         bool inDirective = false;
135                         StringBuilder directive = null;
136                         StringBuilder content = new StringBuilder ();
137                         int idxStart, idxEnd, length;
138                         StreamReader sr;
139
140 #if NET_2_0
141                         if (reader != null)
142                                 sr = reader as StreamReader;
143                         else
144 #endif
145                                 sr = new StreamReader (File.OpenRead (physPath), WebEncoding.FileEncoding);
146                         
147                         using (sr) {
148                                 while ((line = sr.ReadLine ()) != null && cachedType == null) {
149                                         length = line.Length;
150                                         if (length == 0) {
151                                                 content.Append ("\n");
152                                                 continue;
153                                         }
154                                         
155                                         idxStart = line.IndexOf ("<%");
156                                         if (idxStart > -1) {
157                                                 idxEnd = line.IndexOf ("%>");                                           
158                                                 if (idxStart > 0)
159                                                         content.Append (line.Substring (0, idxStart));
160
161                                                 if (directive == null)
162                                                         directive = new StringBuilder ();
163                                                 else
164                                                         directive.Length = 0;
165                                                 
166                                                 if (idxEnd > -1) {
167                                                         directiveFound = true;
168                                                         inDirective = false;
169                                                         directive.Append (line.Substring (idxStart, idxEnd - idxStart + 2));
170                                                         if (idxEnd < length - 2)
171                                                                 content.Append (line.Substring (idxEnd + 2, length - idxEnd - 2));
172                                                 } else {
173                                                         inDirective = true;
174                                                         directiveFound = false;
175                                                         directive.Append (line.Substring (idxStart));
176                                                         continue;
177                                                 }
178                                         }
179
180                                         if (inDirective) {
181                                                 int idx = line.IndexOf ("%>");
182                                                 if (idx > -1) {
183                                                         directive.Append (line.Substring (0, idx + 2));
184                                                         if (idx < length)
185                                                                 content.Append (line.Substring (idx + 2) + "\n");
186                                                         inDirective = false;
187                                                         directiveFound = true;
188                                                 } else {
189                                                         directive.Append (line);
190                                                         continue;
191                                                 }
192                                         }
193                                         
194                                         if (directiveFound) {
195                                                 ParseDirective (directive.ToString ());
196                                                 directiveFound = false;
197                                                 if (gotDefault) {
198                                                         cachedType = CachingCompiler.GetTypeFromCache (physPath);
199                                                         if (cachedType != null)
200                                                                 break;
201                                                 }
202
203                                                 continue;
204                                         }
205
206                                         content.Append (line + "\n");
207                                 }
208                                 directive = null;
209                         }
210
211                         if (!gotDefault)
212                                 throw new ParseException (null, "No @" + DefaultDirectiveName +
213                                                         " directive found");
214
215                         if (cachedType == null)
216                                 this.program = content.ToString ();
217                 }
218
219                 void TagParsed (ILocation location, System.Web.Compilation.TagType tagtype, string tagid, TagAttributes attributes)
220                 {
221                         if (tagtype != System.Web.Compilation.TagType.Directive)
222                                 throw new ParseException (location, "Unexpected tag");
223
224                         if (tagid == null || tagid.Length == 0 || String.Compare (tagid, DefaultDirectiveName, true) == 0) {
225                                 AddDefaultDirective (location, attributes);
226                         } else if (String.Compare (tagid, "Assembly", true) == 0) {
227                                 AddAssemblyDirective (location, attributes);
228                         } else {
229                                 throw new ParseException (location, "Unexpected directive: " + tagid);
230                         }
231                 }
232
233                 void TextParsed (ILocation location, string text)
234                 {
235                         if (text.Trim () != "")
236                                 throw new ParseException (location, "Text not allowed here");
237                 }
238
239                 void ParseError (ILocation location, string message)
240                 {
241                         throw new ParseException (location, message);
242                 }
243
244                 static string GetAndRemove (Hashtable table, string key)
245                 {
246                         string o = table [key] as string;
247                         table.Remove (key);
248                         return o;
249                 }
250                 
251                 void ParseDirective (string line)
252                 {
253                         AspParser parser;
254
255                         using (StringReader input = new StringReader (line)) {
256                                 parser = new AspParser (physPath, input);
257                         }
258                         
259                         parser.Error += new ParseErrorHandler (ParseError);
260                         parser.TagParsed += new TagParsedHandler (TagParsed);
261                         parser.TextParsed += new TextParsedHandler (TextParsed);
262
263                         parser.Parse ();
264                 }
265
266                 internal virtual void AddDefaultDirective (ILocation location, TagAttributes attrs)
267                 {
268 #if NET_2_0
269                         CompilationSection compConfig;
270 #else
271                         CompilationConfiguration compConfig;
272 #endif
273                         compConfig = CompilationConfig;
274                         
275                         if (gotDefault)
276                                 throw new ParseException (location, "duplicate " + DefaultDirectiveName + " directive");
277
278                         gotDefault = true;
279                         Hashtable attributes = attrs.GetDictionary (null);
280                         className = GetAndRemove (attributes, "class");
281                         if (className == null)
282                                 throw new ParseException (null, "No Class attribute found.");
283                         
284                         string d = GetAndRemove (attributes, "debug");
285                         if (d != null) {
286                                 debug = (String.Compare (d, "true", true) == 0);
287                                 if (debug == false && String.Compare (d, "false", true) != 0)
288                                         throw new ParseException (null, "Invalid value for Debug attribute");
289                         } else
290                                 debug = compConfig.Debug;
291
292                         language = GetAndRemove (attributes, "language");
293                         if (language == null)
294                                 language = compConfig.DefaultLanguage;
295
296                         GetAndRemove (attributes, "codebehind");
297                         if (attributes.Count > 0)
298                                 throw new ParseException (location, "Unrecognized attribute in " +
299                                                           DefaultDirectiveName + " directive");
300                 }
301
302                 internal virtual void AddAssemblyDirective (ILocation location, TagAttributes attrs)
303                 {
304                         Hashtable tbl = attrs.GetDictionary (null);
305                         string name = GetAndRemove (tbl, "Name");
306                         string src = GetAndRemove (tbl, "Src");
307                         if (name == null && src == null)
308                                 throw new ParseException (location, "You gotta specify Src or Name");
309
310                         if (name != null && src != null)
311                                 throw new ParseException (location, "Src and Name cannot be used together");
312
313                         if (name != null) {
314                                 AddAssemblyByName (name, location);
315                         } else {
316                                 GetAssemblyFromSource (src, location);
317                         }
318
319                         if (tbl.Count > 0)
320                                 throw new ParseException (location, "Unrecognized attribute in Assembly directive");
321                 }
322
323                 internal virtual void AddAssembly (Assembly assembly, bool fullPath)
324                 {
325                         if (assembly == null)
326                                 throw new ArgumentNullException ("assembly");
327                         
328                         if (anames == null)
329                                 anames = new Hashtable ();
330
331                         string name = assembly.GetName ().Name;
332                         string loc = assembly.Location;
333                         if (fullPath) {
334                                 if (!assemblies.Contains (loc)) {
335                                         assemblies.Add (loc);
336                                 }
337
338                                 anames [name] = loc;
339                                 anames [loc] = assembly;
340                         } else {
341                                 if (!assemblies.Contains (name)) {
342                                         assemblies.Add (name);
343                                 }
344
345                                 anames [name] = assembly;
346                         }
347                 }
348
349                 internal virtual Assembly AddAssemblyByName (string name, ILocation location)
350                 {
351                         if (anames == null)
352                                 anames = new Hashtable ();
353
354                         if (anames.Contains (name)) {
355                                 object o = anames [name];
356                                 if (o is string)
357                                         o = anames [o];
358
359                                 return (Assembly) o;
360                         }
361
362                         Assembly assembly = LoadAssemblyFromBin (name);
363                         if (assembly != null) {
364                                 AddAssembly (assembly, true);
365                                 return assembly;
366                         }
367
368                         Exception ex = null;
369                         try {
370                                 assembly = Assembly.LoadWithPartialName (name);
371                         } catch (Exception e) {
372                                 ex = null;
373                                 assembly = null;
374                         }
375
376                         if (assembly == null)
377                                 throw new ParseException (location, String.Format ("Assembly '{0}' not found", name), ex);
378                         
379                         AddAssembly (assembly, true);
380                         return assembly;
381                 }
382
383                 void AddAssembliesInBin ()
384                 {
385                         foreach (string s in HttpApplication.BinDirectoryAssemblies) {
386                                 try {
387                                         Assembly assembly = Assembly.LoadFrom (s);
388                                         AddAssembly (assembly, true);
389                                 } catch (Exception e) {
390                                         throw new Exception ("Error while loading " + s, e);
391                                 }
392                         }
393                 }
394
395                 Assembly LoadAssemblyFromBin (string name)
396                 {
397                         Assembly assembly = null;
398                         foreach (string dll in HttpApplication.BinDirectoryAssemblies) {
399                                 string fn = Path.GetFileName (dll);
400                                 fn = Path.ChangeExtension (fn, null);
401                                 if (fn != name)
402                                         continue;
403
404                                 assembly = Assembly.LoadFrom (dll);
405                                 return assembly;
406                         }
407                         
408                         return null;
409                 }
410
411                 Assembly GetAssemblyFromSource (string vpath, ILocation location)
412                 {
413                         vpath = UrlUtils.Combine (BaseVirtualDir, vpath);
414                         string realPath = context.Request.MapPath (vpath);
415                         if (!File.Exists (realPath))
416                                 throw new ParseException (location, "File " + vpath + " not found");
417
418                         AddDependency (vpath);
419
420                         CompilerResults result = CachingCompiler.Compile (language, realPath, realPath, assemblies);
421                         if (result.NativeCompilerReturnValue != 0) {
422                                 using (StreamReader sr = new StreamReader (realPath)) {
423                                         throw new CompilationException (realPath, result.Errors, sr.ReadToEnd ());
424                                 }
425                         }
426
427                         AddAssembly (result.CompiledAssembly, true);
428                         return result.CompiledAssembly;
429                 }
430                 
431                 internal Type GetTypeFromBin (string tname)
432                 {
433                         if (tname == null || tname.Length == 0)
434                                 throw new ArgumentNullException ("tname");
435                         
436                         Type result = null;
437                         string typeName;
438                         string assemblyName;
439                         int comma = tname.IndexOf (',');
440                         
441                         if (comma != -1) {
442                                 typeName = tname.Substring (0, comma).Trim ();
443                                 assemblyName = tname.Substring (comma + 1).Trim ();
444                         } else {
445                                 typeName = tname;
446                                 assemblyName = null;
447                         }
448
449                         Type type = null;
450                         Assembly assembly = null;
451                         if (assemblyName != null) {
452                                 assembly = Assembly.Load (assemblyName);
453                                 if (assembly != null)
454                                         type = assembly.GetType (typeName, false);
455                                 if (type != null)
456                                         return type;
457                         }
458                         
459 #if NET_2_0
460                         IList toplevelAssemblies = BuildManager.TopLevelAssemblies;
461                         if (toplevelAssemblies != null && toplevelAssemblies.Count > 0) {
462                                 foreach (Assembly asm in toplevelAssemblies) {
463                                         type = asm.GetType (typeName, false);
464                                         if (type != null) {
465                                                 if (result != null)
466                                                         throw new HttpException (String.Format ("Type {0} is not unique.", typeName));
467                                                 result = type;
468                                         }
469                                 }
470                         }
471 #endif
472
473                         foreach (string dll in HttpApplication.BinDirectoryAssemblies) {
474                                 assembly = Assembly.LoadFrom (dll);
475                                 type = assembly.GetType (typeName, false);
476                                 if (type != null) {
477                                         if (result != null) 
478                                                 throw new HttpException (String.Format ("Type {0} is not unique.", typeName));
479                                                 
480                                         result = type;
481                                 }
482                         }
483
484                         
485                         if (result == null)
486                                 throw new HttpException (String.Format ("Type {0} not found.", typeName));
487
488                         return result;
489                 }
490                 
491                 internal virtual void AddDependency (string filename)
492                 {
493                         if (dependencies == null)
494                                 dependencies = new ArrayList ();
495
496                         if (!dependencies.Contains (filename))
497                                 dependencies.Add (filename);
498                 }
499                 
500                 // Properties
501                 protected abstract string DefaultDirectiveName { get; }
502
503                 internal HttpContext Context {
504                         get { return context; }
505                 }
506
507                 internal string VirtualPath {
508                         get { return vPath; }
509                 }
510
511                 internal string PhysicalPath {
512                         get { return physPath; }
513                 }
514
515                 internal string ClassName {
516                         get { return className; }
517                 }
518
519                 internal bool Debug {
520                         get { return debug; }
521                 }
522
523                 internal string Language {
524                         get { return language; }
525                 }
526
527                 internal string Program {
528                         get {
529                                 if (program != null)
530                                         return program;
531
532                                 return String.Empty;
533                         }
534                 }
535
536                 internal ArrayList Assemblies {
537                         get {
538                                 if (appAssemblyIndex != -1) {
539                                         object o = assemblies [appAssemblyIndex];
540                                         assemblies.RemoveAt (appAssemblyIndex);
541                                         assemblies.Add (o);
542                                         appAssemblyIndex = -1;
543                                 }
544
545                                 return assemblies;
546                         }
547                 }
548
549                 internal ArrayList Dependencies {
550                         get { return dependencies; }
551                 }
552
553                 internal string BaseDir {
554                         get {
555                                 if (baseDir == null)
556                                         baseDir = context.Request.MapPath (BaseVirtualDir);
557
558                                 return baseDir;
559                         }
560                 }
561
562                 internal virtual string BaseVirtualDir {
563                         get {
564                                 if (baseVDir == null)
565                                         baseVDir = UrlUtils.GetDirectory (context.Request.FilePath);
566
567                                 return baseVDir;
568                         }
569                 }
570
571 #if NET_2_0
572                 CompilationSection CompilationConfig {
573                         get {
574                                 return (CompilationSection)WebConfigurationManager.GetSection ("system.web/compilation");
575                         }
576                 }
577
578                 internal TextReader Reader {
579                         get { return reader; }
580                         set { reader = value; }
581                 }
582 #else
583                 internal CompilationConfiguration CompilationConfig {
584                         get {
585                                 if (compilationConfig == null)
586                                         compilationConfig = CompilationConfiguration.GetInstance (context);
587
588                                 return compilationConfig;
589                         }
590                 }
591 #endif
592         }
593 }
594