2007-03-24 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 WaitCallback do_RealProcessRequest;
61                 
62                 QueueManager _queue_manager;
63                 TraceManager _trace_manager;
64                 Cache _cache;
65
66                 static HttpRuntime ()
67                 {
68                         do_RealProcessRequest = new WaitCallback (RealProcessRequest);
69                 }
70
71                 public HttpRuntime ()
72                 {
73                         WebConfigurationManager.Init ();
74                         _queue_manager = new QueueManager ();
75                         _trace_manager = new TraceManager ();
76                         _cache = new Cache ();
77                 }
78
79                 static private HttpRuntime _runtimeInstance {
80                         get {
81                                 HttpRuntime runtime = (HttpRuntime) AppDomain.CurrentDomain.GetData ("HttpRuntime");
82                                 if (runtime == null)
83                                         lock (typeof (HttpRuntime)) {
84                                                 runtime = (HttpRuntime) AppDomain.CurrentDomain.GetData ("HttpRuntime");
85                                                 if (runtime == null) {
86                                                         runtime = new HttpRuntime ();
87                                                         AppDomain.CurrentDomain.SetData ("HttpRuntime", runtime);
88                                                 }
89                                         }
90                                 return runtime;
91                         }
92                 }
93                 static private HttpRuntime _runtime
94                 {
95                         get
96                         {
97                                 if (HttpContext.Current != null)
98                                         return HttpContext.Current.HttpRuntimeInstance;
99                                 else
100                                         return _runtimeInstance;
101                         }
102                 }
103 #else
104                 static QueueManager queue_manager;
105                 static TraceManager trace_manager;
106                 static TimeoutManager timeout_manager;
107                 static Cache cache;
108                 static WaitCallback do_RealProcessRequest;
109
110 #if NET_2_0
111                 static bool assemblyMappingEnabled;
112                 static object assemblyMappingLock = new object ();
113 #endif
114                 
115                 static HttpRuntime ()
116                 {
117 #if NET_2_0
118                         WebConfigurationManager.Init ();
119 #endif
120                         queue_manager = new QueueManager ();
121                         trace_manager = new TraceManager ();
122                         timeout_manager = new TimeoutManager ();
123                         cache = new Cache ();
124                         do_RealProcessRequest = new WaitCallback (RealProcessRequest);
125                 }
126
127 #if ONLY_1_1
128                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
129 #endif
130                 public HttpRuntime ()
131                 {
132                 }
133 #endif
134
135 #region AppDomain handling
136                 //
137                 // http://radio.weblogs.com/0105476/stories/2002/07/12/executingAspxPagesWithoutAWebServer.html
138                 //
139                 public static string AppDomainAppId {
140                         [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.High)]
141                         get {
142                                 //
143                                 // This value should not change across invocations
144                                 //
145                                 string dirname = (string) AppDomain.CurrentDomain.GetData (".appId");
146                                 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
147                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
148                                 }
149                                 return dirname;
150                         }
151                 }
152
153                 // Physical directory for the application
154                 public static string AppDomainAppPath {
155                         get {
156                                 string dirname = (string) AppDomain.CurrentDomain.GetData (".appPath");
157                                 if (SecurityManager.SecurityEnabled) {
158                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
159                                 }
160                                 return dirname;
161                         }
162                 }
163
164                 public static string AppDomainAppVirtualPath {
165                         get {
166                                 return (string) AppDomain.CurrentDomain.GetData (".appVPath");
167                         }
168                 }
169
170                 public static string AppDomainId {
171                         [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.High)]
172                         get {
173                                 return (string) AppDomain.CurrentDomain.GetData (".domainId");
174                         }
175                 }
176
177                 public static string AspInstallDirectory {
178                         get {
179                                 string dirname = (string) AppDomain.CurrentDomain.GetData (".hostingInstallDir");
180                                 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
181                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
182                                 }
183                                 return dirname;
184                         }
185                 }
186 #endregion
187                 
188                 public static string BinDirectory {
189                         get {
190                                 string dirname = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
191                                 if (SecurityManager.SecurityEnabled) {
192                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
193                                 }
194                                 return dirname;
195                         }
196                 }
197
198                 public static Cache Cache {
199                         get {
200                                 return cache;
201                         }
202                 }
203
204                 public static string ClrInstallDirectory {
205                         get {
206                                 string dirname = Path.GetDirectoryName (typeof (Object).Assembly.Location);
207                                 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
208                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
209                                 }
210                                 return dirname;
211                         }
212                 }
213
214                 public static string CodegenDir {
215                         get {
216                                 string dirname = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
217                                 if (SecurityManager.SecurityEnabled) {
218                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
219                                 }
220                                 return dirname;
221                         }
222                 }
223
224                 [MonoTODO]
225                 public static bool IsOnUNCShare {
226                         [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
227                         get {
228                                 throw new NotImplementedException ();
229                         }
230                 }
231
232                 public static string MachineConfigurationDirectory {
233                         get {
234                                 string dirname = Path.GetDirectoryName (ICalls.GetMachineConfigPath ());
235                                 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
236                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
237                                 }
238                                 return dirname;
239                         }
240                 }
241
242                 
243                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
244                 public static void Close ()
245                 {
246                         // Remove all items from cache.
247                 }
248
249                 static void QueuePendingRequests ()
250                 {
251                         HttpWorkerRequest request = queue_manager.GetNextRequest (null);
252                         if (request == null)
253                                 return;
254                         ThreadPool.QueueUserWorkItem (do_RealProcessRequest, request);
255                 }
256
257                 static void RealProcessRequest (object o)
258                 {
259                         HttpContext context = new HttpContext ((HttpWorkerRequest) o);
260                         HttpContext.Current = context;
261
262                         //
263                         // Get application instance (create or reuse an instance of the correct class)
264                         //
265                         HttpApplication app = null;
266                         bool error = false;
267                         try {
268                                 app = HttpApplicationFactory.GetApplication (context);
269                         } catch (Exception e) {
270                                 FinishWithException ((HttpWorkerRequest) o, new HttpException ("", e));
271                                 error = true;
272                         }
273
274                         if (error) {
275                                 context.Request.ReleaseResources ();
276                                 context.Response.ReleaseResources ();
277                                 HttpContext.Current = null;
278                         } else {
279                                 context.ApplicationInstance = app;
280                                 
281                                 //
282                                 // Ask application to service the request
283                                 //
284                                 IHttpAsyncHandler ihah = app;
285
286                                 IAsyncResult appiar = ihah.BeginProcessRequest (context, new AsyncCallback (request_processed), context);
287                                 ihah.EndProcessRequest (appiar);
288
289                                 HttpApplicationFactory.Recycle (app);
290                         }
291                         
292                         QueuePendingRequests ();
293                 }
294                 
295                 //
296                 // ProcessRequest method is executed in the AppDomain of the application
297                 //
298                 // Observations:
299                 //    ProcessRequest does not guarantee that `wr' will be processed synchronously,
300                 //    the request can be queued and processed later.
301                 //
302                 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
303                 public static void ProcessRequest (HttpWorkerRequest wr)
304                 {
305                         if (wr == null)
306                                 throw new ArgumentNullException ("wr");
307                         //
308                         // Queue our request, fetch the next available one from the queue
309                         //
310                         HttpWorkerRequest request = queue_manager.GetNextRequest (wr);
311                         if (request == null)
312                                 return;
313
314                         RealProcessRequest (request);
315                 }
316
317                 //
318                 // Callback to be invoked by IHttpAsyncHandler.BeginProcessRequest
319                 //
320                 static void request_processed (IAsyncResult iar)
321                 {
322                         HttpContext context = (HttpContext) iar.AsyncState;
323
324                         context.Request.ReleaseResources ();
325                         context.Response.ReleaseResources ();
326                 }
327
328                 //
329                 // Called when we are shutting down or we need to reload an application
330                 // that has been modified (touch global.asax) 
331                 //
332                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
333                 public static void UnloadAppDomain ()
334                 {
335                         //
336                         // TODO: call ReleaseResources
337                         //
338                         ThreadPool.QueueUserWorkItem (new WaitCallback (ShutdownAppDomain), null);
339                 }
340
341                 //
342                 // Shuts down the AppDomain
343                 //
344                 static void ShutdownAppDomain (object args)
345                 {
346                         queue_manager.Dispose ();
347                         // This will call Session_End if needed.
348                         Cache.InvokePrivateCallbacks ();
349                         // Kill our application.
350                         HttpApplicationFactory.Dispose ();
351                         ThreadPool.QueueUserWorkItem (new WaitCallback (DoUnload), null);
352                 }
353
354 #if TARGET_J2EE // No unload support for appdomains under Grasshopper
355                 static void DoUnload (object state)
356                 {
357                 }
358 #else
359                 static void DoUnload (object state)
360                 {
361                         AppDomain.Unload (AppDomain.CurrentDomain);
362                 }
363 #endif
364
365                 static string content503 = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n" +
366                         "<html><head>\n<title>503 Server Unavailable</title>\n</head><body>\n" +
367                         "<h1>Server Unavailable</h1>\n" +
368                         "</body></html>\n";
369
370                 static void FinishWithException (HttpWorkerRequest wr, HttpException e)
371                 {
372                         int code = e.GetHttpCode ();
373                         wr.SendStatus (code, HttpWorkerRequest.GetStatusDescription (code));
374                         wr.SendUnknownResponseHeader ("Connection", "close");
375                         wr.SendUnknownResponseHeader ("Date", DateTime.Now.ToUniversalTime ().ToString ("r"));
376                         Encoding enc = Encoding.ASCII;
377                         wr.SendUnknownResponseHeader ("Content-Type", "text/html; charset=" + enc.WebName);
378                         string msg = e.GetHtmlErrorMessage ();
379                         byte [] contentBytes = enc.GetBytes (msg);
380                         wr.SendUnknownResponseHeader ("Content-Length", contentBytes.Length.ToString ());
381                         wr.SendResponseFromMemory (contentBytes, contentBytes.Length);
382                         wr.FlushResponse (true);
383                         wr.CloseConnection ();
384                 }
385
386                 //
387                 // This is called from the QueueManager if a request
388                 // can not be processed (load, no resources, or
389                 // appdomain unload).
390                 //
391                 static internal void FinishUnavailable (HttpWorkerRequest wr)
392                 {
393                         wr.SendStatus (503, "Service unavailable");
394                         wr.SendUnknownResponseHeader ("Connection", "close");
395                         wr.SendUnknownResponseHeader ("Date", DateTime.Now.ToUniversalTime ().ToString ("r"));
396                         Encoding enc = Encoding.ASCII;
397                         wr.SendUnknownResponseHeader ("Content-Type", "text/html; charset=" + enc.WebName);
398                         byte [] contentBytes = enc.GetBytes (content503);
399                         wr.SendUnknownResponseHeader ("Content-Length", contentBytes.Length.ToString ());
400                         wr.SendResponseFromMemory (contentBytes, contentBytes.Length);
401                         wr.FlushResponse (true);
402                         wr.CloseConnection ();
403                 }
404
405 #if NET_2_0 && !TARGET_J2EE
406                 static internal void WritePreservationFile (Assembly asm, string genericNameBase)
407                 {
408                         if (asm == null)
409                                 throw new ArgumentNullException ("asm");
410                         if (String.IsNullOrEmpty (genericNameBase))
411                                 throw new ArgumentNullException ("genericNameBase");
412
413                         string compiled = Path.Combine (AppDomain.CurrentDomain.SetupInformation.DynamicBase,
414                                                         genericNameBase + ".compiled");
415                         PreservationFile pf = new PreservationFile ();
416                         try {
417                                 pf.VirtualPath = String.Format ("/{0}/", genericNameBase);
418
419                                 AssemblyName an = asm.GetName ();
420                                 pf.Assembly = an.Name;
421                                 pf.ResultType = BuildResultTypeCode.TopLevelAssembly;
422                                 pf.Save (compiled);
423                         } catch (Exception ex) {
424                                 throw new HttpException (
425                                         String.Format ("Failed to write preservation file {0}", genericNameBase + ".compiled"),
426                                         ex);
427                         }
428                 }
429                 
430                 static Assembly ResolveAssemblyHandler(object sender, ResolveEventArgs e)
431                 {
432                         AssemblyName an = new AssemblyName (e.Name);
433                         string dynamic_base = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
434                         string compiled = Path.Combine (dynamic_base, an.Name + ".compiled");
435
436                         if (!File.Exists (compiled))
437                                 return null;
438
439                         PreservationFile pf;
440                         try {
441                                 pf = new PreservationFile (compiled);
442                         } catch (Exception ex) {
443                                 throw new HttpException (
444                                         String.Format ("Failed to read preservation file {0}", an.Name + ".compiled"),
445                                         ex);
446                         }
447                         
448                         Assembly ret = null;
449                         try {
450                                 string asmPath = Path.Combine (dynamic_base, pf.Assembly + ".dll");
451                                 ret = Assembly.LoadFrom (asmPath);
452                         } catch (Exception) {
453                                 // ignore
454                         }
455                         
456                         return ret;
457                 }
458                 
459                 internal static void EnableAssemblyMapping (bool enable)
460                 {
461                         lock (assemblyMappingLock) {
462                                 if (assemblyMappingEnabled == enable)
463                                         return;
464                                 if (enable)
465                                         AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler (ResolveAssemblyHandler);
466                                 else
467                                         AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler (ResolveAssemblyHandler);
468                         }
469                 }
470 #endif
471                 
472                 internal static TraceManager TraceManager {
473                         get {
474                                 return trace_manager;
475                         }
476                 }
477
478 #if !TARGET_JVM
479                 internal static TimeoutManager TimeoutManager {
480                         get {
481                                 return timeout_manager;
482                         }
483                 }
484 #endif
485         }
486 }