2006-01-04 Chris Toshok <toshok@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 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 !CONFIGURATION_2_0
62                 CompilationConfiguration compilationConfig;
63 #endif
64                 int appAssemblyIndex = -1;
65                 Type cachedType;
66
67                 protected SimpleWebHandlerParser (HttpContext context, string virtualPath, string physicalPath)
68                 {
69                         cachedType = CachingCompiler.GetTypeFromCache (physicalPath);
70                         if (cachedType != null)
71                                 return; // We don't need anything else.
72
73                         this.context = context;
74                         this.vPath = virtualPath;
75                         this.physPath = physicalPath;
76                         AddDependency (physicalPath);
77
78                         assemblies = new ArrayList ();
79                         string location = Context.ApplicationInstance.AssemblyLocation;
80                         if (location != typeof (TemplateParser).Assembly.Location)
81                                 appAssemblyIndex = assemblies.Add (location);
82
83 #if CONFIGURATION_2_0
84                         bool addAssembliesInBin = false;
85                         foreach (AssemblyInfo info in CompilationConfig.Assemblies) {
86                                 if (info.Assembly == "*")
87                                         addAssembliesInBin = true;
88                                 else
89                                         assemblies.Add (info.Assembly);
90                         }
91                         if (addAssembliesInBin)
92                                 AddAssembliesInBin ();
93 #else
94                         assemblies.AddRange (CompilationConfig.Assemblies);
95                         if (CompilationConfig.AssembliesInBin)
96                                 AddAssembliesInBin ();
97 #endif
98
99 #if !CONFIGURATION_2_0
100 #endif
101
102                         language = CompilationConfig.DefaultLanguage;
103
104                         GetDirectivesAndContent ();
105                 }
106
107                 protected Type GetCompiledTypeFromCache ()
108                 {
109                         return cachedType;
110                 }
111
112                 void GetDirectivesAndContent ()
113                 {
114                         StreamReader reader = new StreamReader (File.OpenRead (physPath));
115                         string line;
116                         bool directiveFound = false;
117                         StringBuilder content = new StringBuilder ();
118
119                         while ((line = reader.ReadLine ()) != null && cachedType == null) {
120                                 string trimmed = line.Trim ();
121                                 if (!directiveFound && trimmed == String.Empty)
122                                         continue;
123                                 
124                                 if (trimmed.StartsWith ("<")) {
125                                         ParseDirective (trimmed);
126                                         directiveFound = true;
127                                         if (gotDefault) {
128                                                 cachedType = CachingCompiler.GetTypeFromCache (physPath);
129                                                 if (cachedType != null)
130                                                         break;
131                                         }
132
133                                         continue;
134                                 }
135
136                                 content.Append (line + "\n");
137                                 content.Append (reader.ReadToEnd ());
138                         }
139                         reader.Close ();
140
141                         if (!gotDefault)
142                                 throw new ParseException (null, "No @" + DefaultDirectiveName +
143                                                         " directive found");
144
145                         if (cachedType == null)
146                                 this.program = content.ToString ();
147                 }
148
149                 void TagParsed (ILocation location, System.Web.Compilation.TagType tagtype, string tagid, TagAttributes attributes)
150                 {
151                         if (tagtype != System.Web.Compilation.TagType.Directive)
152                                 throw new ParseException (location, "Unexpected tag");
153
154                         if (String.Compare (tagid, DefaultDirectiveName, true) == 0) {
155                                 AddDefaultDirective (location, attributes);
156                         } else if (String.Compare (tagid, "Assembly", true) == 0) {
157                                 AddAssemblyDirective (location, attributes);
158                         } else {
159                                 throw new ParseException (location, "Unexpected directive: " + tagid);
160                         }
161                 }
162
163                 void TextParsed (ILocation location, string text)
164                 {
165                         if (text.Trim () != "")
166                                 throw new ParseException (location, "Text not allowed here");
167                 }
168
169                 void ParseError (ILocation location, string message)
170                 {
171                         throw new ParseException (location, message);
172                 }
173
174                 static string GetAndRemove (Hashtable table, string key)
175                 {
176                         string o = table [key] as string;
177                         table.Remove (key);
178                         return o;
179                 }
180                 
181                 void ParseDirective (string line)
182                 {
183                         AspParser parser = new AspParser (physPath, new StringReader (line));
184                         parser.Error += new ParseErrorHandler (ParseError);
185                         parser.TagParsed += new TagParsedHandler (TagParsed);
186                         parser.TextParsed += new TextParsedHandler (TextParsed);
187
188                         parser.Parse ();
189                 }
190
191                 internal virtual void AddDefaultDirective (ILocation location, TagAttributes attrs)
192                 {
193                         if (gotDefault)
194                                 throw new ParseException (location, "duplicate " + DefaultDirectiveName + " directive");
195
196                         gotDefault = true;
197                         Hashtable attributes = attrs.GetDictionary (null);
198                         className = GetAndRemove (attributes, "class");
199                         if (className == null)
200                                 throw new ParseException (null, "No Class attribute found.");
201                         
202                         string d = GetAndRemove (attributes, "debug");
203                         if (d != null) {
204                                 debug = (String.Compare (d, "true", true) == 0);
205                                 if (debug == false && String.Compare (d, "false", true) != 0)
206                                         throw new ParseException (null, "Invalid value for Debug attribute");
207                         }
208
209                         language = GetAndRemove (attributes, "language");
210                         if (language == null)
211                                 language = CompilationConfig.DefaultLanguage;
212
213                         GetAndRemove (attributes, "codebehind");
214                         if (attributes.Count > 0)
215                                 throw new ParseException (location, "Unrecognized attribute in " +
216                                                           DefaultDirectiveName + " directive");
217                 }
218
219                 internal virtual void AddAssemblyDirective (ILocation location, TagAttributes attrs)
220                 {
221                         Hashtable tbl = attrs.GetDictionary (null);
222                         string name = GetAndRemove (tbl, "Name");
223                         string src = GetAndRemove (tbl, "Src");
224                         if (name == null && src == null)
225                                 throw new ParseException (location, "You gotta specify Src or Name");
226
227                         if (name != null && src != null)
228                                 throw new ParseException (location, "Src and Name cannot be used together");
229
230                         if (name != null) {
231                                 AddAssemblyByName (name, location);
232                         } else {
233                                 GetAssemblyFromSource (src, location);
234                         }
235
236                         if (tbl.Count > 0)
237                                 throw new ParseException (location, "Unrecognized attribute in Assembly directive");
238                 }
239
240                 internal virtual void AddAssembly (Assembly assembly, bool fullPath)
241                 {
242                         if (anames == null)
243                                 anames = new Hashtable ();
244
245                         string name = assembly.GetName ().Name;
246                         string loc = assembly.Location;
247                         if (fullPath) {
248                                 if (!assemblies.Contains (loc)) {
249                                         assemblies.Add (loc);
250                                 }
251
252                                 anames [name] = loc;
253                                 anames [loc] = assembly;
254                         } else {
255                                 if (!assemblies.Contains (name)) {
256                                         assemblies.Add (name);
257                                 }
258
259                                 anames [name] = assembly;
260                         }
261                 }
262
263                 internal virtual Assembly AddAssemblyByName (string name, ILocation location)
264                 {
265                         if (anames == null)
266                                 anames = new Hashtable ();
267
268                         if (anames.Contains (name)) {
269                                 object o = anames [name];
270                                 if (o is string)
271                                         o = anames [o];
272
273                                 return (Assembly) o;
274                         }
275
276                         Assembly assembly = LoadAssemblyFromBin (name);
277                         if (assembly != null) {
278                                 AddAssembly (assembly, true);
279                                 return assembly;
280                         }
281
282                         try {
283                                 assembly = Assembly.LoadWithPartialName (name);
284                         } catch (Exception e) {
285                                 throw new ParseException (location, "Assembly " + name + " not found", e);
286                         }
287
288                         AddAssembly (assembly, true);
289                         return assembly;
290                 }
291
292                 void AddAssembliesInBin ()
293                 {
294                         if (!Directory.Exists (PrivateBinPath))
295                                 return;
296
297                         string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
298                         foreach (string dll in binDlls) {
299                                 try {
300                                         Assembly assembly = Assembly.LoadFrom (dll);
301                                         AddAssembly (assembly, true);
302                                 } catch (Exception e) {
303                                         throw new Exception ("Error while loading " + dll, e);
304                                 }
305                         }
306                 }
307
308                 Assembly LoadAssemblyFromBin (string name)
309                 {
310                         Assembly assembly;
311                         if (!Directory.Exists (PrivateBinPath))
312                                 return null;
313
314                         string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
315                         foreach (string dll in binDlls) {
316                                 string fn = Path.GetFileName (dll);
317                                 fn = Path.ChangeExtension (fn, null);
318                                 if (fn != name)
319                                         continue;
320
321                                 assembly = Assembly.LoadFrom (dll);
322                                 return assembly;
323                         }
324
325                         return null;
326                 }
327
328                 Assembly GetAssemblyFromSource (string vpath, ILocation location)
329                 {
330                         vpath = UrlUtils.Combine (BaseVirtualDir, vpath);
331                         string realPath = context.Request.MapPath (vpath);
332                         if (!File.Exists (realPath))
333                                 throw new ParseException (location, "File " + vpath + " not found");
334
335                         AddDependency (realPath);
336
337                         CompilerResults result = CachingCompiler.Compile (language, realPath, realPath, assemblies);
338                         if (result.NativeCompilerReturnValue != 0) {
339                                 StreamReader reader = new StreamReader (realPath);
340                                 throw new CompilationException (realPath, result.Errors, reader.ReadToEnd ());
341                         }
342
343                         AddAssembly (result.CompiledAssembly, true);
344                         return result.CompiledAssembly;
345                 }
346                 
347                 internal Type GetTypeFromBin (string typeName)
348                 {
349                         if (!Directory.Exists (PrivateBinPath))
350                                 throw new HttpException (String.Format ("Type {0} not found.", typeName));
351
352                         string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
353                         Type result = null;
354                         foreach (string dll in binDlls) {
355                                 Assembly assembly = Assembly.LoadFrom (dll);
356                                 Type type = assembly.GetType (typeName, false);
357                                 if (type != null) {
358                                         if (result != null) 
359                                                 throw new HttpException (String.Format ("Type {0} is not unique.", typeName));
360
361                                         result = type;
362                                 } 
363                         }
364
365                         if (result == null)
366                                 throw new HttpException (String.Format ("Type {0} not found.", typeName));
367
368                         return result;
369                 }
370                 
371                 internal virtual void AddDependency (string filename)
372                 {
373                         if (dependencies == null)
374                                 dependencies = new ArrayList ();
375
376                         if (!dependencies.Contains (filename))
377                                 dependencies.Add (filename);
378                 }
379                 
380                 // Properties
381                 protected abstract string DefaultDirectiveName { get; }
382
383                 internal HttpContext Context {
384                         get { return context; }
385                 }
386
387                 internal string VirtualPath {
388                         get { return vPath; }
389                 }
390
391                 internal string PhysicalPath {
392                         get { return physPath; }
393                 }
394
395                 internal string ClassName {
396                         get { return className; }
397                 }
398
399                 internal bool Debug {
400                         get { return debug; }
401                 }
402
403                 internal string Language {
404                         get { return language; }
405                 }
406
407                 internal string Program {
408                         get { return program; }
409                 }
410
411                 internal ArrayList Assemblies {
412                         get {
413                                 if (appAssemblyIndex != -1) {
414                                         object o = assemblies [appAssemblyIndex];
415                                         assemblies.RemoveAt (appAssemblyIndex);
416                                         assemblies.Add (o);
417                                         appAssemblyIndex = -1;
418                                 }
419
420                                 return assemblies;
421                         }
422                 }
423
424                 internal ArrayList Dependencies {
425                         get { return dependencies; }
426                 }
427
428                 internal string PrivateBinPath {
429                         get {
430                                 if (privateBinPath != null)
431                                         return privateBinPath;
432
433                                 AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
434                                 privateBinPath = setup.PrivateBinPath;
435                                         
436                                 if (!Path.IsPathRooted (privateBinPath)) {
437                                         string appbase = setup.ApplicationBase;
438                                         if (appbase.StartsWith ("file://")) {
439                                                 appbase = appbase.Substring (7);
440                                                 if (Path.DirectorySeparatorChar != '/')
441                                                         appbase = appbase.Replace ('/', Path.DirectorySeparatorChar);
442                                         }
443                                         privateBinPath = Path.Combine (appbase, privateBinPath);
444                                 }
445
446                                 return privateBinPath;
447                         }
448                 }
449
450                 internal string BaseDir {
451                         get {
452                                 if (baseDir == null)
453                                         baseDir = context.Request.MapPath (BaseVirtualDir);
454
455                                 return baseDir;
456                         }
457                 }
458
459                 internal virtual string BaseVirtualDir {
460                         get {
461                                 if (baseVDir == null)
462                                         baseVDir = UrlUtils.GetDirectory (context.Request.FilePath);
463
464                                 return baseVDir;
465                         }
466                 }
467
468 #if CONFIGURATION_2_0
469                 CompilationSection CompilationConfig {
470                         get {
471                                 return (CompilationSection)WebConfigurationManager.GetWebApplicationSection ("system.web/compilation");
472                         }
473                 }
474 #else
475                 internal CompilationConfiguration CompilationConfig {
476                         get {
477                                 if (compilationConfig == null)
478                                         compilationConfig = CompilationConfiguration.GetInstance (context);
479
480                                 return compilationConfig;
481                         }
482                 }
483 #endif
484         }
485 }
486