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