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