svn path=/branches/mono-1-1-9/mcs/; revision=51216
[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 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.CodeDom.Compiler;
33 using System.Collections;
34 using System.IO;
35 using System.Reflection;
36 using System.Text;
37 using System.Web;
38 using System.Web.Compilation;
39 using System.Web.Configuration;
40 using System.Web.Util;
41
42 namespace System.Web.UI
43 {
44         public abstract class SimpleWebHandlerParser
45         {
46                 HttpContext context;
47                 string vPath;
48                 string physPath;
49                 string className;
50                 string codeBehind;
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                 CompilationConfiguration compilationConfig;
62                 int appAssemblyIndex = -1;
63                 Type cachedType;
64
65                 protected SimpleWebHandlerParser (HttpContext context, string virtualPath, string physicalPath)
66                 {
67                         cachedType = CachingCompiler.GetTypeFromCache (physicalPath);
68                         if (cachedType != null)
69                                 return; // We don't need anything else.
70
71                         this.context = context;
72                         this.vPath = virtualPath;
73                         this.physPath = physicalPath;
74                         AddDependency (physicalPath);
75
76                         assemblies = new ArrayList ();
77                         string location = Context.ApplicationInstance.AssemblyLocation;
78                         if (location != typeof (TemplateParser).Assembly.Location)
79                                 appAssemblyIndex = assemblies.Add (location);
80
81                         assemblies.AddRange (CompilationConfig.Assemblies);
82                         if (CompilationConfig.AssembliesInBin)
83                                 AddAssembliesInBin ();
84
85                         language = CompilationConfig.DefaultLanguage;
86
87                         GetDirectivesAndContent ();
88                 }
89
90                 protected Type GetCompiledTypeFromCache ()
91                 {
92                         return cachedType;
93                 }
94
95                 void GetDirectivesAndContent ()
96                 {
97                         StreamReader reader = new StreamReader (File.OpenRead (physPath));
98                         string line;
99                         bool directiveFound = false;
100                         StringBuilder content = new StringBuilder ();
101
102                         while ((line = reader.ReadLine ()) != null && cachedType == null) {
103                                 string trimmed = line.Trim ();
104                                 if (!directiveFound && trimmed == String.Empty)
105                                         continue;
106                                 
107                                 if (trimmed.StartsWith ("<")) {
108                                         ParseDirective (trimmed);
109                                         directiveFound = true;
110                                         if (gotDefault) {
111                                                 cachedType = CachingCompiler.GetTypeFromCache (physPath);
112                                                 if (cachedType != null)
113                                                         break;
114                                         }
115
116                                         continue;
117                                 }
118
119                                 content.Append (line + "\n");
120                                 content.Append (reader.ReadToEnd ());
121                         }
122                         reader.Close ();
123
124                         if (!gotDefault)
125                                 throw new ParseException (null, "No @" + DefaultDirectiveName +
126                                                         " directive found");
127
128                         if (cachedType == null)
129                                 this.program = content.ToString ();
130                 }
131
132                 void TagParsed (ILocation location, System.Web.Compilation.TagType tagtype, string tagid, TagAttributes attributes)
133                 {
134                         if (tagtype != System.Web.Compilation.TagType.Directive)
135                                 throw new ParseException (location, "Unexpected tag");
136
137                         if (String.Compare (tagid, DefaultDirectiveName, true) == 0) {
138                                 AddDefaultDirective (location, attributes);
139                         } else if (String.Compare (tagid, "Assembly", true) == 0) {
140                                 AddAssemblyDirective (location, attributes);
141                         } else {
142                                 throw new ParseException (location, "Unexpected directive: " + tagid);
143                         }
144                 }
145
146                 void TextParsed (ILocation location, string text)
147                 {
148                         if (text.Trim () != "")
149                                 throw new ParseException (location, "Text not allowed here");
150                 }
151
152                 void ParseError (ILocation location, string message)
153                 {
154                         throw new ParseException (location, message);
155                 }
156
157                 static string GetAndRemove (Hashtable table, string key)
158                 {
159                         string o = table [key] as string;
160                         table.Remove (key);
161                         return o;
162                 }
163                 
164                 void ParseDirective (string line)
165                 {
166                         AspParser parser = new AspParser (physPath, new StringReader (line));
167                         parser.Error += new ParseErrorHandler (ParseError);
168                         parser.TagParsed += new TagParsedHandler (TagParsed);
169                         parser.TextParsed += new TextParsedHandler (TextParsed);
170
171                         parser.Parse ();
172                 }
173
174                 internal virtual void AddDefaultDirective (ILocation location, TagAttributes attrs)
175                 {
176                         if (gotDefault)
177                                 throw new ParseException (location, "duplicate " + DefaultDirectiveName + " directive");
178
179                         gotDefault = true;
180                         Hashtable attributes = attrs.GetDictionary (null);
181                         className = GetAndRemove (attributes, "class");
182                         if (className == null)
183                                 throw new ParseException (null, "No Class attribute found.");
184                         
185                         string d = GetAndRemove (attributes, "debug");
186                         if (d != null) {
187                                 debug = (String.Compare (d, "true", true) == 0);
188                                 if (debug == false && String.Compare (d, "false", true) != 0)
189                                         throw new ParseException (null, "Invalid value for Debug attribute");
190                         }
191
192                         language = GetAndRemove (attributes, "language");
193                         if (language == null)
194                                 language = CompilationConfig.DefaultLanguage;
195
196                         codeBehind = GetAndRemove (attributes, "codebehind");
197                         if (attributes.Count > 0)
198                                 throw new ParseException (location, "Unrecognized attribute in " +
199                                                           DefaultDirectiveName + " directive");
200                 }
201
202                 internal virtual void AddAssemblyDirective (ILocation location, TagAttributes attrs)
203                 {
204                         Hashtable tbl = attrs.GetDictionary (null);
205                         string name = GetAndRemove (tbl, "Name");
206                         string src = GetAndRemove (tbl, "Src");
207                         if (name == null && src == null)
208                                 throw new ParseException (location, "You gotta specify Src or Name");
209
210                         if (name != null && src != null)
211                                 throw new ParseException (location, "Src and Name cannot be used together");
212
213                         if (name != null) {
214                                 AddAssemblyByName (name, location);
215                         } else {
216                                 GetAssemblyFromSource (src, location);
217                         }
218
219                         if (tbl.Count > 0)
220                                 throw new ParseException (location, "Unrecognized attribute in Assembly directive");
221                 }
222
223                 internal virtual void AddAssembly (Assembly assembly, bool fullPath)
224                 {
225                         if (anames == null)
226                                 anames = new Hashtable ();
227
228                         string name = assembly.GetName ().Name;
229                         string loc = assembly.Location;
230                         if (fullPath) {
231                                 if (!assemblies.Contains (loc)) {
232                                         assemblies.Add (loc);
233                                 }
234
235                                 anames [name] = loc;
236                                 anames [loc] = assembly;
237                         } else {
238                                 if (!assemblies.Contains (name)) {
239                                         assemblies.Add (name);
240                                 }
241
242                                 anames [name] = assembly;
243                         }
244                 }
245
246                 internal virtual Assembly AddAssemblyByName (string name, ILocation location)
247                 {
248                         if (anames == null)
249                                 anames = new Hashtable ();
250
251                         if (anames.Contains (name)) {
252                                 object o = anames [name];
253                                 if (o is string)
254                                         o = anames [o];
255
256                                 return (Assembly) o;
257                         }
258
259                         Assembly assembly = LoadAssemblyFromBin (name);
260                         if (assembly != null) {
261                                 AddAssembly (assembly, true);
262                                 return assembly;
263                         }
264
265                         try {
266                                 assembly = Assembly.LoadWithPartialName (name);
267                         } catch (Exception e) {
268                                 throw new ParseException (location, "Assembly " + name + " not found", e);
269                         }
270
271                         AddAssembly (assembly, true);
272                         return assembly;
273                 }
274
275                 void AddAssembliesInBin ()
276                 {
277                         if (!Directory.Exists (PrivateBinPath))
278                                 return;
279
280                         string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
281                         foreach (string dll in binDlls) {
282                                 try {
283                                         Assembly assembly = Assembly.LoadFrom (dll);
284                                         AddAssembly (assembly, true);
285                                 } catch (Exception e) {
286                                         throw new Exception ("Error while loading " + dll, e);
287                                 }
288                         }
289                 }
290
291                 Assembly LoadAssemblyFromBin (string name)
292                 {
293                         Assembly assembly;
294                         if (!Directory.Exists (PrivateBinPath))
295                                 return null;
296
297                         string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
298                         foreach (string dll in binDlls) {
299                                 string fn = Path.GetFileName (dll);
300                                 fn = Path.ChangeExtension (fn, null);
301                                 if (fn != name)
302                                         continue;
303
304                                 assembly = Assembly.LoadFrom (dll);
305                                 return assembly;
306                         }
307
308                         return null;
309                 }
310
311                 Assembly GetAssemblyFromSource (string vpath, ILocation location)
312                 {
313                         vpath = UrlUtils.Combine (BaseVirtualDir, vpath);
314                         string realPath = context.Request.MapPath (vpath);
315                         if (!File.Exists (realPath))
316                                 throw new ParseException (location, "File " + vpath + " not found");
317
318                         AddDependency (realPath);
319
320                         CompilerResults result = CachingCompiler.Compile (language, realPath, realPath, assemblies);
321                         if (result.NativeCompilerReturnValue != 0) {
322                                 StreamReader reader = new StreamReader (realPath);
323                                 throw new CompilationException (realPath, result.Errors, reader.ReadToEnd ());
324                         }
325
326                         AddAssembly (result.CompiledAssembly, true);
327                         return result.CompiledAssembly;
328                 }
329                 
330                 internal Type GetTypeFromBin (string typeName)
331                 {
332                         if (!Directory.Exists (PrivateBinPath))
333                                 throw new HttpException (String.Format ("Type {0} not found.", typeName));
334
335                         string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
336                         Type result = null;
337                         foreach (string dll in binDlls) {
338                                 Assembly assembly = Assembly.LoadFrom (dll);
339                                 Type type = assembly.GetType (typeName, false);
340                                 if (type != null) {
341                                         if (result != null) 
342                                                 throw new HttpException (String.Format ("Type {0} is not unique.", typeName));
343
344                                         result = type;
345                                 } 
346                         }
347
348                         if (result == null)
349                                 throw new HttpException (String.Format ("Type {0} not found.", typeName));
350
351                         return result;
352                 }
353                 
354                 internal virtual void AddDependency (string filename)
355                 {
356                         if (dependencies == null)
357                                 dependencies = new ArrayList ();
358
359                         if (!dependencies.Contains (filename))
360                                 dependencies.Add (filename);
361                 }
362                 
363                 // Properties
364                 protected abstract string DefaultDirectiveName { get; }
365
366                 internal HttpContext Context {
367                         get { return context; }
368                 }
369
370                 internal string VirtualPath {
371                         get { return vPath; }
372                 }
373
374                 internal string PhysicalPath {
375                         get { return physPath; }
376                 }
377
378                 internal string ClassName {
379                         get { return className; }
380                 }
381
382                 internal string CodeBehind {
383                         get { return codeBehind; }
384                 }
385
386                 internal bool Debug {
387                         get { return debug; }
388                 }
389
390                 internal string Language {
391                         get { return language; }
392                 }
393
394                 internal string Program {
395                         get { return program; }
396                 }
397
398                 internal ArrayList Assemblies {
399                         get {
400                                 if (appAssemblyIndex != -1) {
401                                         object o = assemblies [appAssemblyIndex];
402                                         assemblies.RemoveAt (appAssemblyIndex);
403                                         assemblies.Add (o);
404                                         appAssemblyIndex = -1;
405                                 }
406
407                                 return assemblies;
408                         }
409                 }
410
411                 internal ArrayList Dependencies {
412                         get { return dependencies; }
413                 }
414
415                 internal string PrivateBinPath {
416                         get {
417                                 if (privateBinPath != null)
418                                         return privateBinPath;
419
420                                 AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
421                                 privateBinPath = setup.PrivateBinPath;
422                                         
423                                 if (!Path.IsPathRooted (privateBinPath)) {
424                                         string appbase = setup.ApplicationBase;
425                                         if (appbase.StartsWith ("file://")) {
426                                                 appbase = appbase.Substring (7);
427                                                 if (Path.DirectorySeparatorChar != '/')
428                                                         appbase = appbase.Replace ('/', Path.DirectorySeparatorChar);
429                                         }
430                                         privateBinPath = Path.Combine (appbase, privateBinPath);
431                                 }
432
433                                 return privateBinPath;
434                         }
435                 }
436
437                 internal string BaseDir {
438                         get {
439                                 if (baseDir == null)
440                                         baseDir = context.Request.MapPath (BaseVirtualDir);
441
442                                 return baseDir;
443                         }
444                 }
445
446                 internal virtual string BaseVirtualDir {
447                         get {
448                                 if (baseVDir == null)
449                                         baseVDir = UrlUtils.GetDirectory (context.Request.FilePath);
450
451                                 return baseVDir;
452                         }
453                 }
454
455                 internal CompilationConfiguration CompilationConfig {
456                         get {
457                                 if (compilationConfig == null)
458                                         compilationConfig = CompilationConfiguration.GetInstance (context);
459
460                                 return compilationConfig;
461                         }
462                 }
463         }
464 }
465