2007-08-31 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web / HttpRuntime.cs
1 //
2 // System.Web.HttpRuntime.cs 
3 // 
4 // Author:
5 //      Miguel de Icaza (miguel@novell.com)
6 //
7 //
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 //
31 // TODO: Call HttpRequest.CloseInputStream when we finish a request, as we are using the IntPtr stream.
32 //
33 using System.IO;
34 using System.Text;
35 using System.Globalization;
36 using System.Collections;
37 using System.Reflection;
38 using System.Security;
39 using System.Security.Permissions;
40 using System.Web.Caching;
41 using System.Web.Configuration;
42 using System.Web.UI;
43 using System.Web.Util;
44 using System.Threading;
45
46 #if NET_2_0 && !TARGET_JVM
47 using System.CodeDom.Compiler;
48 using System.Web.Compilation;
49 #endif
50
51 namespace System.Web {
52         
53         // CAS - no InheritanceDemand here as the class is sealed
54         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
55         public sealed class HttpRuntime {
56 #if TARGET_J2EE
57                 static QueueManager queue_manager { get { return _runtime._queue_manager; } }
58                 static TraceManager trace_manager { get { return _runtime._trace_manager; } }
59                 static Cache cache { get { return _runtime._cache; } }
60                 static Cache internalCache { get { return _runtime._internalCache; } }
61                 static WaitCallback do_RealProcessRequest;
62                 
63                 QueueManager _queue_manager;
64                 TraceManager _trace_manager;
65                 Cache _cache;
66                 Cache _internalCache;
67
68                 static HttpRuntime ()
69                 {
70                         do_RealProcessRequest = new WaitCallback (RealProcessRequest);
71                 }
72
73                 public HttpRuntime ()
74                 {
75                         WebConfigurationManager.Init ();
76                         _queue_manager = new QueueManager ();
77                         _trace_manager = new TraceManager ();
78                         _cache = new Cache ();
79                         _internalCache = new Cache();
80                 }
81
82                 static private HttpRuntime _runtimeInstance {
83                         get {
84                                 HttpRuntime runtime = (HttpRuntime) AppDomain.CurrentDomain.GetData ("HttpRuntime");
85                                 if (runtime == null)
86                                         lock (typeof (HttpRuntime)) {
87                                                 runtime = (HttpRuntime) AppDomain.CurrentDomain.GetData ("HttpRuntime");
88                                                 if (runtime == null) {
89                                                         runtime = new HttpRuntime ();
90                                                         AppDomain.CurrentDomain.SetData ("HttpRuntime", runtime);
91                                                 }
92                                         }
93                                 return runtime;
94                         }
95                 }
96                 static private HttpRuntime _runtime
97                 {
98                         get
99                         {
100                                 if (HttpContext.Current != null)
101                                         return HttpContext.Current.HttpRuntimeInstance;
102                                 else
103                                         return _runtimeInstance;
104                         }
105                 }
106 #else
107                 static QueueManager queue_manager;
108                 static TraceManager trace_manager;
109                 static TimeoutManager timeout_manager;
110                 static Cache cache;
111                 static Cache internalCache;
112                 static WaitCallback do_RealProcessRequest;
113                 static Exception initialException;
114                 static bool firstRun;
115                 
116 #if NET_2_0
117                 static bool assemblyMappingEnabled;
118                 static object assemblyMappingLock = new object ();
119                 static object appOfflineLock = new object ();
120 #endif
121                 
122                 static HttpRuntime ()
123                 {
124                         firstRun = true;
125 #if NET_2_0
126                         try {
127                                 WebConfigurationManager.Init ();
128                         } catch (Exception ex) {
129                                 initialException = ex;
130                         }
131                         
132 #endif
133
134                         // The classes in whose constructors exceptions may be thrown, should be handled the same way QueueManager
135                         // and TraceManager are below. The constructors themselves MUST NOT throw any exceptions - we MUST be sure
136                         // the objects are created here. The exceptions will be dealt with below, in RealProcessRequest.
137                         queue_manager = new QueueManager ();
138                         if (queue_manager.HasException)
139                                 initialException = queue_manager.InitialException;
140
141                         trace_manager = new TraceManager ();
142                         if (trace_manager.HasException)
143                                         initialException = trace_manager.InitialException;
144
145                         timeout_manager = new TimeoutManager ();
146                         cache = new Cache ();
147                         internalCache = new Cache ();
148                         do_RealProcessRequest = new WaitCallback (RealProcessRequest);
149                 }
150
151 #if ONLY_1_1
152                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
153 #endif
154                 public HttpRuntime ()
155                 {
156
157                 }
158 #endif
159                 
160 #region AppDomain handling
161                 //
162                 // http://radio.weblogs.com/0105476/stories/2002/07/12/executingAspxPagesWithoutAWebServer.html
163                 //
164                 public static string AppDomainAppId {
165                         [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.High)]
166                         get {
167                                 //
168                                 // This value should not change across invocations
169                                 //
170                                 string dirname = (string) AppDomain.CurrentDomain.GetData (".appId");
171                                 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
172                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
173                                 }
174                                 return dirname;
175                         }
176                 }
177
178                 // Physical directory for the application
179                 public static string AppDomainAppPath {
180                         get {
181                                 string dirname = (string) AppDomain.CurrentDomain.GetData (".appPath");
182                                 if (SecurityManager.SecurityEnabled) {
183                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
184                                 }
185                                 return dirname;
186                         }
187                 }
188
189                 public static string AppDomainAppVirtualPath {
190                         get {
191                                 return (string) AppDomain.CurrentDomain.GetData (".appVPath");
192                         }
193                 }
194
195                 public static string AppDomainId {
196                         [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.High)]
197                         get {
198                                 return (string) AppDomain.CurrentDomain.GetData (".domainId");
199                         }
200                 }
201
202                 public static string AspInstallDirectory {
203                         get {
204                                 string dirname = (string) AppDomain.CurrentDomain.GetData (".hostingInstallDir");
205                                 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
206                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
207                                 }
208                                 return dirname;
209                         }
210                 }
211 #endregion
212                 
213                 public static string BinDirectory {
214                         get {
215                                 string dirname = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
216                                 if (SecurityManager.SecurityEnabled) {
217                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
218                                 }
219                                 return dirname;
220                         }
221                 }
222
223                 public static Cache Cache {
224                         get {
225                                 return cache;
226                         }
227                 }
228
229                 internal static Cache InternalCache {
230                         get {
231                                 return internalCache;
232                         }
233                 }
234                 
235                 public static string ClrInstallDirectory {
236                         get {
237                                 string dirname = Path.GetDirectoryName (typeof (Object).Assembly.Location);
238                                 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
239                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
240                                 }
241                                 return dirname;
242                         }
243                 }
244
245                 public static string CodegenDir {
246                         get {
247                                 string dirname = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
248                                 if (SecurityManager.SecurityEnabled) {
249                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
250                                 }
251                                 return dirname;
252                         }
253                 }
254
255                 [MonoTODO]
256                 public static bool IsOnUNCShare {
257                         [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
258                         get {
259                                 throw new NotImplementedException ();
260                         }
261                 }
262
263                 public static string MachineConfigurationDirectory {
264                         get {
265                                 string dirname = Path.GetDirectoryName (ICalls.GetMachineConfigPath ());
266                                 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
267                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
268                                 }
269                                 return dirname;
270                         }
271                 }
272
273                 
274                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
275                 public static void Close ()
276                 {
277                         // Remove all items from cache.
278                 }
279
280                 static void QueuePendingRequests ()
281                 {
282                         HttpWorkerRequest request = queue_manager.GetNextRequest (null);
283                         if (request == null)
284                                 return;
285                         ThreadPool.QueueUserWorkItem (do_RealProcessRequest, request);
286                 }
287
288 #if NET_2_0
289                 static readonly string[] app_offline_files = {"app_offline.htm", "App_Offline.htm", "APP_OFFLINE.HTM"};
290                 static FileSystemWatcher app_offline_watcher;
291
292                 static void AppOfflineFileRenamed (object sender, RenamedEventArgs args)
293                 {
294                         AppOfflineFileChanged (sender, args);
295                 }
296                 
297                 static void AppOfflineFileChanged (object sender, FileSystemEventArgs args)
298                 {
299                         lock (appOfflineLock) {
300                                 HttpApplicationFactory.DisableWatchers ();
301                                 app_offline_watcher.EnableRaisingEvents = false;
302                                 
303                                 // Restart application
304                                 HttpRuntime.UnloadAppDomain();
305                         }
306                 }
307                 
308                 static bool AppIsOffline (HttpContext context)
309                 {
310                         string app_path = context.Request.PhysicalApplicationPath;
311                         string aof_path = null;
312                         bool haveOfflineFile = false;
313
314                         foreach (string aof in app_offline_files) {
315                                 aof_path = Path.Combine (app_path, aof);
316                                 if (File.Exists (aof_path)) {
317                                         haveOfflineFile = true;
318                                         break;
319                                 }
320                         }
321
322                         lock (appOfflineLock) {
323                                 if (!haveOfflineFile) {
324                                         if (HttpApplicationFactory.ApplicationDisabled) {
325                                                 HttpApplicationFactory.ApplicationDisabled = false;
326                                                 HttpApplicationFactory.EnableWatchers ();
327                                         }
328                                         
329                                         return false;
330                                 }
331
332                                 HttpApplicationFactory.ApplicationDisabled = true;
333                                 HttpApplicationFactory.DisableWatchers ();
334
335                                 FileSystemEventHandler seh = new FileSystemEventHandler (AppOfflineFileChanged);
336                                 RenamedEventHandler reh = new RenamedEventHandler (AppOfflineFileRenamed);
337                                 
338                                 app_offline_watcher = new FileSystemWatcher ();
339                                 app_offline_watcher.Path = Path.GetDirectoryName (aof_path);
340                                 app_offline_watcher.Filter = Path.GetFileName (aof_path);
341                                 app_offline_watcher.NotifyFilter |= NotifyFilters.Size;
342                                 app_offline_watcher.Deleted += seh;
343                                 app_offline_watcher.Changed += seh;
344                                 app_offline_watcher.Renamed += reh;
345                                 app_offline_watcher.EnableRaisingEvents = true;
346                         }
347
348                         HttpResponse response = context.Response;
349                         response.Clear ();
350                         response.ContentType = "text/html";
351                         response.ExpiresAbsolute = DateTime.UtcNow;
352                         response.TransmitFile (aof_path, true);
353                         
354                         context.Request.ReleaseResources ();
355                         context.Response.ReleaseResources ();
356                         HttpContext.Current = null;
357                         
358                         return true;
359                 }
360 #endif
361                 
362                 static void RealProcessRequest (object o)
363                 {
364                         HttpContext context = new HttpContext ((HttpWorkerRequest) o);
365                         HttpContext.Current = context;
366
367                         bool error = false;
368 #if !TARGET_J2EE
369                         if (firstRun) {
370                                 firstRun = false;
371                                 if (initialException != null) {
372                                         FinishWithException ((HttpWorkerRequest) o, new HttpException ("Initial exception", initialException));
373                                         error = true;
374                                 }
375                         }
376 #endif
377
378 #if NET_2_0
379                         if (AppIsOffline (context))
380                                 return;
381 #endif
382                         
383                         //
384                         // Get application instance (create or reuse an instance of the correct class)
385                         //
386                         HttpApplication app = null;
387                         if (!error) {
388                                 try {
389                                         app = HttpApplicationFactory.GetApplication (context);
390                                 } catch (Exception e) {
391                                         FinishWithException ((HttpWorkerRequest) o, new HttpException ("", e));
392                                         error = true;
393                                 }
394                         }
395                         
396                         if (error) {
397                                 context.Request.ReleaseResources ();
398                                 context.Response.ReleaseResources ();
399                                 HttpContext.Current = null;
400                         } else {
401                                 context.ApplicationInstance = app;
402                                 
403                                 //
404                                 // Ask application to service the request
405                                 //
406                                 IHttpAsyncHandler ihah = app;
407
408                                 IAsyncResult appiar = ihah.BeginProcessRequest (context, new AsyncCallback (request_processed), context);
409                                 ihah.EndProcessRequest (appiar);
410
411                                 HttpApplicationFactory.Recycle (app);
412                         }
413                         
414                         QueuePendingRequests ();
415                 }
416                 
417                 //
418                 // ProcessRequest method is executed in the AppDomain of the application
419                 //
420                 // Observations:
421                 //    ProcessRequest does not guarantee that `wr' will be processed synchronously,
422                 //    the request can be queued and processed later.
423                 //
424                 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
425                 public static void ProcessRequest (HttpWorkerRequest wr)
426                 {
427                         if (wr == null)
428                                 throw new ArgumentNullException ("wr");
429                         //
430                         // Queue our request, fetch the next available one from the queue
431                         //
432                         HttpWorkerRequest request = queue_manager.GetNextRequest (wr);
433                         if (request == null)
434                                 return;
435
436                         RealProcessRequest (request);
437                 }
438
439                 //
440                 // Callback to be invoked by IHttpAsyncHandler.BeginProcessRequest
441                 //
442                 static void request_processed (IAsyncResult iar)
443                 {
444                         HttpContext context = (HttpContext) iar.AsyncState;
445
446                         context.Request.ReleaseResources ();
447                         context.Response.ReleaseResources ();
448                 }
449
450                 //
451                 // Called when we are shutting down or we need to reload an application
452                 // that has been modified (touch global.asax) 
453                 //
454                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
455                 public static void UnloadAppDomain ()
456                 {
457                         //
458                         // TODO: call ReleaseResources
459                         //
460                         ThreadPool.QueueUserWorkItem (new WaitCallback (ShutdownAppDomain), null);
461                 }
462
463                 //
464                 // Shuts down the AppDomain
465                 //
466                 static void ShutdownAppDomain (object args)
467                 {
468                         queue_manager.Dispose ();
469                         // This will call Session_End if needed.
470                         InternalCache.InvokePrivateCallbacks ();
471                         // Kill our application.
472                         HttpApplicationFactory.Dispose ();
473                         ThreadPool.QueueUserWorkItem (new WaitCallback (DoUnload), null);
474                 }
475
476                 static void DoUnload (object state)
477                 {
478                         if (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") == null)
479                                 System.Web.Hosting.ApplicationHost.ClearDynamicBaseDirectory (
480                                         AppDomain.CurrentDomain.SetupInformation.DynamicBase
481                                 );
482 #if TARGET_J2EE
483                         // No unload support for appdomains under Grasshopper
484 #else
485                         AppDomain.Unload (AppDomain.CurrentDomain);
486 #endif
487                 }
488
489                 static string content503 = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n" +
490                         "<html><head>\n<title>503 Server Unavailable</title>\n</head><body>\n" +
491                         "<h1>Server Unavailable</h1>\n" +
492                         "</body></html>\n";
493
494                 static void FinishWithException (HttpWorkerRequest wr, HttpException e)
495                 {
496                         int code = e.GetHttpCode ();
497                         wr.SendStatus (code, HttpWorkerRequest.GetStatusDescription (code));
498                         wr.SendUnknownResponseHeader ("Connection", "close");
499                         wr.SendUnknownResponseHeader ("Date", DateTime.Now.ToUniversalTime ().ToString ("r"));
500                         Encoding enc = Encoding.ASCII;
501                         wr.SendUnknownResponseHeader ("Content-Type", "text/html; charset=" + enc.WebName);
502                         string msg = e.GetHtmlErrorMessage ();
503                         byte [] contentBytes = enc.GetBytes (msg);
504                         wr.SendUnknownResponseHeader ("Content-Length", contentBytes.Length.ToString ());
505                         wr.SendResponseFromMemory (contentBytes, contentBytes.Length);
506                         wr.FlushResponse (true);
507                         wr.CloseConnection ();
508                 }
509
510                 //
511                 // This is called from the QueueManager if a request
512                 // can not be processed (load, no resources, or
513                 // appdomain unload).
514                 //
515                 static internal void FinishUnavailable (HttpWorkerRequest wr)
516                 {
517                         wr.SendStatus (503, "Service unavailable");
518                         wr.SendUnknownResponseHeader ("Connection", "close");
519                         wr.SendUnknownResponseHeader ("Date", DateTime.Now.ToUniversalTime ().ToString ("r"));
520                         Encoding enc = Encoding.ASCII;
521                         wr.SendUnknownResponseHeader ("Content-Type", "text/html; charset=" + enc.WebName);
522                         byte [] contentBytes = enc.GetBytes (content503);
523                         wr.SendUnknownResponseHeader ("Content-Length", contentBytes.Length.ToString ());
524                         wr.SendResponseFromMemory (contentBytes, contentBytes.Length);
525                         wr.FlushResponse (true);
526                         wr.CloseConnection ();
527                 }
528
529 #if NET_2_0 && !TARGET_J2EE
530                 static internal void WritePreservationFile (Assembly asm, string genericNameBase)
531                 {
532                         if (asm == null)
533                                 throw new ArgumentNullException ("asm");
534                         if (String.IsNullOrEmpty (genericNameBase))
535                                 throw new ArgumentNullException ("genericNameBase");
536
537                         string compiled = Path.Combine (AppDomain.CurrentDomain.SetupInformation.DynamicBase,
538                                                         genericNameBase + ".compiled");
539                         PreservationFile pf = new PreservationFile ();
540                         try {
541                                 pf.VirtualPath = String.Format ("/{0}/", genericNameBase);
542
543                                 AssemblyName an = asm.GetName ();
544                                 pf.Assembly = an.Name;
545                                 pf.ResultType = BuildResultTypeCode.TopLevelAssembly;
546                                 pf.Save (compiled);
547                         } catch (Exception ex) {
548                                 throw new HttpException (
549                                         String.Format ("Failed to write preservation file {0}", genericNameBase + ".compiled"),
550                                         ex);
551                         }
552                 }
553                 
554                 static Assembly ResolveAssemblyHandler(object sender, ResolveEventArgs e)
555                 {
556                         AssemblyName an = new AssemblyName (e.Name);
557                         string dynamic_base = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
558                         string compiled = Path.Combine (dynamic_base, an.Name + ".compiled");
559
560                         if (!File.Exists (compiled))
561                                 return null;
562
563                         PreservationFile pf;
564                         try {
565                                 pf = new PreservationFile (compiled);
566                         } catch (Exception ex) {
567                                 throw new HttpException (
568                                         String.Format ("Failed to read preservation file {0}", an.Name + ".compiled"),
569                                         ex);
570                         }
571                         
572                         Assembly ret = null;
573                         try {
574                                 string asmPath = Path.Combine (dynamic_base, pf.Assembly + ".dll");
575                                 ret = Assembly.LoadFrom (asmPath);
576                         } catch (Exception) {
577                                 // ignore
578                         }
579                         
580                         return ret;
581                 }
582                 
583                 internal static void EnableAssemblyMapping (bool enable)
584                 {
585                         lock (assemblyMappingLock) {
586                                 if (assemblyMappingEnabled == enable)
587                                         return;
588                                 if (enable)
589                                         AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler (ResolveAssemblyHandler);
590                                 else
591                                         AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler (ResolveAssemblyHandler);
592                         }
593                 }
594 #endif
595                 
596                 internal static TraceManager TraceManager {
597                         get {
598                                 return trace_manager;
599                         }
600                 }
601
602 #if !TARGET_JVM
603                 internal static TimeoutManager TimeoutManager {
604                         get {
605                                 return timeout_manager;
606                         }
607                 }
608 #endif
609         }
610 }