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