Fix for the issue of getting occasional -5875 error on the server when
[mono.git] / mcs / class / System.Web / System.Web.SessionState_2.0 / SessionStateModule.cs
1 //
2 // System.Web.SessionState.SesionStateModule
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //      Stefan Görling (stefan@gorling.se)
7 //      Jackson Harper (jackson@ximian.com)
8 //      Marek Habersack (grendello@gmail.com)
9 //
10 // Copyright (C) 2002-2006 Novell, Inc (http://www.novell.com)
11 // (C) 2003 Stefan Görling (http://www.gorling.se)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 #if NET_2_0
34 using System.Collections.Specialized;
35 using System.Web.Configuration;
36 using System.Web.Caching;
37 using System.Web.Util;
38 using System.Security.Cryptography;
39 using System.Security.Permissions;
40 using System.Threading;
41 using System.Configuration;
42
43 namespace System.Web.SessionState
44 {       
45         // CAS - no InheritanceDemand here as the class is sealed
46         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
47         public sealed class SessionStateModule : IHttpModule
48         {
49                 class CallbackState
50                 {
51                         public readonly HttpContext Context;
52                         public readonly AutoResetEvent AutoEvent;
53                         public readonly string SessionId;
54                         public readonly bool IsReadOnly;
55
56                         public CallbackState (HttpContext context, AutoResetEvent e, string sessionId, bool isReadOnly) {
57                                 this.Context = context;
58                                 this.AutoEvent = e;
59                                 this.SessionId = sessionId;
60                                 this.IsReadOnly = isReadOnly;
61                         }
62                 }
63
64                 internal const string HeaderName = "AspFilterSessionId";
65                 internal const string CookielessFlagName = "_SessionIDManager_IsCookieLess";
66
67                 SessionStateSection config;
68
69                 SessionStateStoreProviderBase handler;
70                 ISessionIDManager idManager;
71                 bool supportsExpiration;
72
73                 HttpApplication app;
74
75                 // Store state
76                 bool storeLocked;
77                 TimeSpan storeLockAge;
78                 object storeLockId;
79                 SessionStateActions storeSessionAction;
80
81                 // Session state
82                 SessionStateStoreData storeData;
83                 HttpSessionStateContainer container;
84
85                 // config
86                 TimeSpan executionTimeout;
87                 //int executionTimeoutMS;
88
89                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
90                 public SessionStateModule () {
91                 }
92
93                 public void Dispose () {
94                         app.BeginRequest -= new EventHandler (OnBeginRequest);
95                         app.AcquireRequestState -= new EventHandler (OnAcquireRequestState);
96                         app.ReleaseRequestState -= new EventHandler (OnReleaseRequestState);
97                         app.EndRequest -= new EventHandler (OnEndRequest);
98                         handler.Dispose ();
99                 }
100
101                 [EnvironmentPermission (SecurityAction.Assert, Read = "MONO_XSP_STATIC_SESSION")]
102                 public void Init (HttpApplication app) {
103
104                         config = (SessionStateSection) WebConfigurationManager.GetSection ("system.web/sessionState");
105
106                         ProviderSettings settings;
107                         switch (config.Mode) {
108                         case SessionStateMode.Custom:
109                                 settings = config.Providers [config.CustomProvider];
110                                 if (settings == null)
111                                         throw new HttpException (String.Format ("Cannot find '{0}' provider.", config.CustomProvider));
112                                 break;
113                         case SessionStateMode.Off:
114                                 return;
115 #if TARGET_J2EE
116                         default:
117                                 config = new SessionStateSection ();
118                                 config.Mode = SessionStateMode.Custom;
119                                 config.CustomProvider = "ServletSessionStateStore";
120                                 config.SessionIDManagerType = "Mainsoft.Web.SessionState.ServletSessionIDManager";
121                                 config.Providers.Add (new ProviderSettings ("ServletSessionStateStore", "Mainsoft.Web.SessionState.ServletSessionStateStoreProvider"));
122                                 goto case SessionStateMode.Custom;
123 #else
124                         case SessionStateMode.InProc:
125                                 settings = new ProviderSettings (null, typeof (SessionInProcHandler).AssemblyQualifiedName);
126                                 break;
127                         case SessionStateMode.SQLServer:
128                         //settings = new ProviderSettings (null, typeof (SessionInProcHandler).AssemblyQualifiedName);
129                         //break;
130                         default:
131                                 throw new NotImplementedException (String.Format ("The mode '{0}' is not implemented.", config.Mode));
132                         case SessionStateMode.StateServer:
133                                 settings = new ProviderSettings (null, typeof (SessionStateServerHandler).AssemblyQualifiedName);
134                                 break;
135 #endif
136                         }
137
138                         handler = (SessionStateStoreProviderBase) ProvidersHelper.InstantiateProvider (settings, typeof (SessionStateStoreProviderBase));
139
140                         if (String.IsNullOrEmpty(config.SessionIDManagerType)) {
141                                 idManager = new SessionIDManager ();
142                         } else {
143                                 Type idManagerType = HttpApplication.LoadType (config.SessionIDManagerType, true);
144                                 idManager = (ISessionIDManager)Activator.CreateInstance (idManagerType);
145                         }
146
147                         try {                           
148                                 idManager.Initialize ();
149                         } catch (Exception ex) {
150                                 throw new HttpException ("Failed to initialize session ID manager.", ex);
151                         }
152
153                         supportsExpiration = handler.SetItemExpireCallback (OnSessionExpired);
154                         HttpRuntimeSection runtime = WebConfigurationManager.GetSection ("system.web/httpRuntime") as HttpRuntimeSection;
155                         executionTimeout = runtime.ExecutionTimeout;
156                         //executionTimeoutMS = executionTimeout.Milliseconds;
157
158                         this.app = app;
159
160                         app.BeginRequest += new EventHandler (OnBeginRequest);
161                         app.AcquireRequestState += new EventHandler (OnAcquireRequestState);
162                         app.ReleaseRequestState += new EventHandler (OnReleaseRequestState);
163                         app.EndRequest += new EventHandler (OnEndRequest);
164                 }
165
166                 internal static bool IsCookieLess (HttpContext context, SessionStateSection config) {
167                         if (config.Cookieless == HttpCookieMode.UseCookies)
168                                 return false;
169                         if (config.Cookieless == HttpCookieMode.UseUri)
170                                 return true;
171                         object cookieless = context.Items [CookielessFlagName];
172                         if (cookieless == null)
173                                 return false;
174                         return (bool) cookieless;
175                 }
176
177                 void OnBeginRequest (object o, EventArgs args) {
178                         HttpApplication application = (HttpApplication) o;
179                         HttpContext context = application.Context;
180                         string base_path = context.Request.BaseVirtualDir;
181                         string id = UrlUtils.GetSessionId (base_path);
182
183                         if (id == null)
184                                 return;
185
186                         string new_path = UrlUtils.RemoveSessionId (base_path, context.Request.FilePath);
187                         context.Request.SetFilePath (new_path);
188                         context.Request.SetHeader (HeaderName, id);
189                         context.Response.SetAppPathModifier (String.Concat ("(", id, ")"));
190                 }
191
192                 void OnAcquireRequestState (object o, EventArgs args) {
193 #if TRACE
194                         Console.WriteLine ("SessionStateModule.OnAcquireRequestState (hash {0})", this.GetHashCode ().ToString ("x"));
195 #endif
196                         HttpApplication application = (HttpApplication) o;
197                         HttpContext context = application.Context;
198
199                         if (!(context.Handler is IRequiresSessionState)) {
200 #if TRACE
201                                 Console.WriteLine ("Handler ({0}) does not require session state", context.Handler);
202 #endif
203                                 return;
204                         }
205                         bool isReadOnly = (context.Handler is IReadOnlySessionState);
206
207                         bool supportSessionIDReissue;
208                         if (idManager.InitializeRequest (context, false, out supportSessionIDReissue))
209                                 return; // Redirected, will come back here in a while
210                         string sessionId = idManager.GetSessionID (context);
211
212
213                         handler.InitializeRequest (context);
214
215                         GetStoreData (context, sessionId, isReadOnly);
216
217                         bool isNew = false;
218                         if (storeData == null && !storeLocked) {
219                                 isNew = true;
220                                 sessionId = idManager.CreateSessionID (context);
221 #if TRACE
222                                 Console.WriteLine ("New session ID allocated: {0}", sessionId);
223 #endif
224                                 bool redirected;
225                                 bool cookieAdded;
226                                 idManager.SaveSessionID (context, sessionId, out redirected, out cookieAdded);
227                                 if (redirected) {
228                                         if (supportSessionIDReissue)
229                                                 handler.CreateUninitializedItem (context, sessionId, (int)config.Timeout.TotalMinutes);
230                                         context.Response.End ();
231                                         return;
232                                 }
233                                 else
234                                         storeData = handler.CreateNewStoreData (context, (int)config.Timeout.TotalMinutes);
235                         }
236                         else if (storeData == null && storeLocked) {
237                                 WaitForStoreUnlock (context, sessionId, isReadOnly);
238                         }
239                         else if (storeData != null &&
240                                  !storeLocked &&
241                                  storeSessionAction == SessionStateActions.InitializeItem &&
242                                  IsCookieLess (context, config)) {
243                                 storeData = handler.CreateNewStoreData (context, (int)config.Timeout.TotalMinutes);
244                         }
245
246                         container = CreateContainer (sessionId, storeData, isNew, isReadOnly);
247                         SessionStateUtility.AddHttpSessionStateToContext (app.Context, container);
248                         if (isNew)
249                                 OnSessionStart ();
250                 }
251
252                 void OnReleaseRequestState (object o, EventArgs args) {
253
254 #if TRACE
255                         Console.WriteLine ("SessionStateModule.OnReleaseRequestState (hash {0})", this.GetHashCode ().ToString ("x"));
256 #endif
257
258                         HttpApplication application = (HttpApplication) o;
259                         HttpContext context = application.Context;
260                         if (!(context.Handler is IRequiresSessionState))
261                                 return;
262
263 #if TRACE
264                         Console.WriteLine ("\tsessionId == {0}", container.SessionID);
265                         Console.WriteLine ("\trequest path == {0}", context.Request.FilePath);
266                         Console.WriteLine ("\tHandler ({0}) requires session state", context.Handler);
267 #endif
268                         try {
269                                 if (!container.IsAbandoned) {
270 #if TRACE
271                                         Console.WriteLine ("\tnot abandoned");
272 #endif
273                                         if (!container.IsReadOnly) {
274 #if TRACE
275                                                 Console.WriteLine ("\tnot read only, storing and releasing");
276 #endif
277                                                 handler.SetAndReleaseItemExclusive (context, container.SessionID, storeData, storeLockId, false);
278                                         }
279                                         else {
280 #if TRACE
281                                                 Console.WriteLine ("\tread only, releasing");
282 #endif
283                                                 handler.ReleaseItemExclusive (context, container.SessionID, storeLockId);
284                                         }
285                                         handler.ResetItemTimeout (context, container.SessionID);
286                                 }
287                                 else {
288                                         handler.ReleaseItemExclusive (context, container.SessionID, storeLockId);
289                                         handler.RemoveItem (context, container.SessionID, storeLockId, storeData);
290                                         if (supportsExpiration)
291                                                 // Make sure the expiration handler is not called after we will have raised
292                                                 // the session end event.
293                                                 handler.SetItemExpireCallback (null);
294                                         SessionStateUtility.RaiseSessionEnd (container, this, args);
295                                 }
296                                 SessionStateUtility.RemoveHttpSessionStateFromContext (context);
297                         }
298                         finally {
299                                 container = null;
300                                 storeData = null;
301                         }
302                 }
303
304                 void OnEndRequest (object o, EventArgs args) {
305                         if (handler == null)
306                                 return;
307
308                         if (container != null)
309                                 OnReleaseRequestState (o, args);
310
311                         HttpApplication application = o as HttpApplication;
312                         if (application == null)
313                                 return;
314                         if (handler != null)
315                                 handler.EndRequest (application.Context);
316                 }
317
318                 void GetStoreData (HttpContext context, string sessionId, bool isReadOnly) {
319                         storeData = (isReadOnly) ?
320                                 handler.GetItem (context,
321                                                                  sessionId,
322                                                                  out storeLocked,
323                                                                  out storeLockAge,
324                                                                  out storeLockId,
325                                                                  out storeSessionAction)
326                                                                  :
327                                 handler.GetItemExclusive (context,
328                                                                           sessionId,
329                                                                           out storeLocked,
330                                                                           out storeLockAge,
331                                                                           out storeLockId,
332                                                                           out storeSessionAction);
333                 }
334
335                 void WaitForStoreUnlock (HttpContext context, string sessionId, bool isReadonly) {
336                         AutoResetEvent are = new AutoResetEvent (false);
337                         TimerCallback tc = new TimerCallback (StoreUnlockWaitCallback);
338                         CallbackState cs = new CallbackState (context, are, sessionId, isReadonly);
339                         using (Timer timer = new Timer (tc, cs, 500, 500)) {
340                                 try {
341                                         are.WaitOne (executionTimeout, false);
342                                 }
343                                 catch {
344                                         storeData = null;
345                                 }
346                         }
347                 }
348
349                 void StoreUnlockWaitCallback (object s) {
350                         CallbackState state = (CallbackState) s;
351
352                         GetStoreData (state.Context, state.SessionId, state.IsReadOnly);
353
354                         if (storeData == null && storeLocked && (storeLockAge > executionTimeout)) {
355                                 handler.ReleaseItemExclusive (state.Context, state.SessionId, storeLockId);
356                                 state.AutoEvent.Set ();
357                         }
358                         else if (storeData != null && !storeLocked)
359                                 state.AutoEvent.Set ();
360                 }
361
362                 HttpSessionStateContainer CreateContainer (string sessionId, SessionStateStoreData data, bool isNew, bool isReadOnly) {
363                         if (data == null)
364                                 return new HttpSessionStateContainer (
365                                         sessionId, null, null, 0, isNew,
366                                         config.Cookieless, config.Mode, isReadOnly);
367                         
368                         return new HttpSessionStateContainer (
369                                 sessionId,
370                                 data.Items,
371                                 data.StaticObjects,
372                                 data.Timeout,
373                                 isNew,
374                                 config.Cookieless,
375                                 config.Mode,
376                                 isReadOnly);
377                 }
378
379                 void OnSessionExpired (string id, SessionStateStoreData item) {
380                         SessionStateUtility.RaiseSessionEnd (
381                                 CreateContainer (id, item, false, true),
382                                 this, EventArgs.Empty);
383                 }
384
385                 void OnSessionStart () {
386                         if (Start != null)
387                                 Start (this, EventArgs.Empty);
388                 }
389
390                 public event EventHandler Start;
391
392                 // This event is public, but only Session_[On]End in global.asax will be invoked if present.
393                 public event EventHandler End;
394         }
395 }
396 #endif