Merge pull request #347 from JamesB7/master
[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.ComponentModel;
36 using System.Web.Configuration;
37 using System.Web.Caching;
38 using System.Web.Util;
39 using System.Security.Permissions;
40 using System.Threading;
41 using System.Configuration;
42 using System.Diagnostics;
43
44 namespace System.Web.SessionState
45 {       
46         // CAS - no InheritanceDemand here as the class is sealed
47         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
48         public sealed class SessionStateModule : IHttpModule
49         {
50                 class CallbackState
51                 {
52                         public readonly HttpContext Context;
53                         public readonly AutoResetEvent AutoEvent;
54                         public readonly string SessionId;
55                         public readonly bool IsReadOnly;
56
57                         public CallbackState (HttpContext context, AutoResetEvent e, string sessionId, bool isReadOnly) {
58                                 this.Context = context;
59                                 this.AutoEvent = e;
60                                 this.SessionId = sessionId;
61                                 this.IsReadOnly = isReadOnly;
62                         }
63                 }
64
65                 internal const string HeaderName = "AspFilterSessionId";
66                 internal const string CookielessFlagName = "_SessionIDManager_IsCookieLess";
67
68                 static readonly object startEvent = new object ();
69                 static readonly object endEvent = new object ();
70                 
71                 SessionStateSection config;
72
73                 SessionStateStoreProviderBase handler;
74                 ISessionIDManager idManager;
75                 bool supportsExpiration;
76
77                 HttpApplication app;
78
79                 // Store state
80                 bool storeLocked;
81                 TimeSpan storeLockAge;
82                 object storeLockId;
83                 SessionStateActions storeSessionAction;
84                 bool storeIsNew;
85                 
86                 // Session state
87                 SessionStateStoreData storeData;
88                 HttpSessionStateContainer container;
89
90                 // config
91                 TimeSpan executionTimeout;
92                 //int executionTimeoutMS;
93
94                 EventHandlerList events = new EventHandlerList ();
95                 
96                 public event EventHandler Start {
97                         add { events.AddHandler (startEvent, value); }
98                         remove { events.RemoveHandler (startEvent, value); }
99                 }
100
101                 // This event is public, but only Session_[On]End in global.asax will be invoked if present.
102                 public event EventHandler End {
103                         add { events.AddHandler (endEvent, value); }
104                         remove { events.RemoveHandler (endEvent, value); }
105                 }
106
107                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
108                 public SessionStateModule () {
109                 }
110
111                 public void Dispose () {
112                         app.BeginRequest -= new EventHandler (OnBeginRequest);
113                         app.AcquireRequestState -= new EventHandler (OnAcquireRequestState);
114                         app.ReleaseRequestState -= new EventHandler (OnReleaseRequestState);
115                         app.EndRequest -= new EventHandler (OnEndRequest);
116                         handler.Dispose ();
117                 }
118
119                 [EnvironmentPermission (SecurityAction.Assert, Read = "MONO_XSP_STATIC_SESSION")]
120                 public void Init (HttpApplication app)
121                 {
122                         config = (SessionStateSection) WebConfigurationManager.GetSection ("system.web/sessionState");
123
124                         ProviderSettings settings;
125                         switch (config.Mode) {
126                                 case SessionStateMode.Custom:
127                                         settings = config.Providers [config.CustomProvider];
128                                         if (settings == null)
129                                                 throw new HttpException (String.Format ("Cannot find '{0}' provider.", config.CustomProvider));
130                                         break;
131                                 case SessionStateMode.Off:
132                                         return;
133 #if TARGET_J2EE
134                                 default:
135                                         config = new SessionStateSection ();
136                                         config.Mode = SessionStateMode.Custom;
137                                         config.CustomProvider = "ServletSessionStateStore";
138                                         config.SessionIDManagerType = "Mainsoft.Web.SessionState.ServletSessionIDManager";
139                                         config.Providers.Add (new ProviderSettings ("ServletSessionStateStore", "Mainsoft.Web.SessionState.ServletSessionStateStoreProvider"));
140                                         goto case SessionStateMode.Custom;
141 #else
142                                 case SessionStateMode.InProc:
143                                         settings = new ProviderSettings (null, typeof (SessionInProcHandler).AssemblyQualifiedName);
144                                         break;
145
146                                 case SessionStateMode.SQLServer:
147                                         settings = new ProviderSettings (null, typeof (SessionSQLServerHandler).AssemblyQualifiedName);
148                                         break;
149
150                                 case SessionStateMode.StateServer:
151                                         settings = new ProviderSettings (null, typeof (SessionStateServerHandler).AssemblyQualifiedName);
152                                         break;
153
154                                 default:
155                                         throw new NotImplementedException (String.Format ("The mode '{0}' is not implemented.", config.Mode));
156                         
157 #endif
158                         }
159
160                         handler = (SessionStateStoreProviderBase) ProvidersHelper.InstantiateProvider (settings, typeof (SessionStateStoreProviderBase));
161
162                         if (String.IsNullOrEmpty(config.SessionIDManagerType)) {
163                                 idManager = new SessionIDManager ();
164                         } else {
165                                 Type idManagerType = HttpApplication.LoadType (config.SessionIDManagerType, true);
166                                 idManager = (ISessionIDManager)Activator.CreateInstance (idManagerType);
167                         }
168
169                         try {                           
170                                 idManager.Initialize ();
171                         } catch (Exception ex) {
172                                 throw new HttpException ("Failed to initialize session ID manager.", ex);
173                         }
174
175                         supportsExpiration = handler.SetItemExpireCallback (OnSessionExpired);
176                         HttpRuntimeSection runtime = HttpRuntime.Section;
177                         executionTimeout = runtime.ExecutionTimeout;
178                         //executionTimeoutMS = executionTimeout.Milliseconds;
179
180                         this.app = app;
181
182                         app.BeginRequest += new EventHandler (OnBeginRequest);
183                         app.AcquireRequestState += new EventHandler (OnAcquireRequestState);
184                         app.ReleaseRequestState += new EventHandler (OnReleaseRequestState);
185                         app.EndRequest += new EventHandler (OnEndRequest);
186                 }
187
188                 internal static bool IsCookieLess (HttpContext context, SessionStateSection config) {
189                         if (config.Cookieless == HttpCookieMode.UseCookies)
190                                 return false;
191                         if (config.Cookieless == HttpCookieMode.UseUri)
192                                 return true;
193                         object cookieless = context.Items [CookielessFlagName];
194                         if (cookieless == null)
195                                 return false;
196                         return (bool) cookieless;
197                 }
198
199                 void OnBeginRequest (object o, EventArgs args)
200                 {
201                         HttpApplication application = (HttpApplication) o;
202                         HttpContext context = application.Context;
203                         string file_path = context.Request.FilePath;
204                         string base_path = VirtualPathUtility.GetDirectory (file_path);
205                         string id = UrlUtils.GetSessionId (base_path);
206
207                         if (id == null)
208                                 return;
209
210                         string new_path = UrlUtils.RemoveSessionId (base_path, file_path);
211                         context.Request.SetFilePath (new_path);
212                         context.Request.SetHeader (HeaderName, id);
213                         context.Response.SetAppPathModifier (id);
214                 }
215
216                 void OnAcquireRequestState (object o, EventArgs args) {
217                         Trace.WriteLine ("SessionStateModule.OnAcquireRequestState (hash " + this.GetHashCode ().ToString ("x") + ")");
218                         HttpApplication application = (HttpApplication) o;
219                         HttpContext context = application.Context;
220
221                         if (!(context.Handler is IRequiresSessionState)) {
222                                 Trace.WriteLine ("Handler (" + context.Handler + ") does not require session state");
223                                 return;
224                         }
225                         bool isReadOnly = (context.Handler is IReadOnlySessionState);
226
227                         bool supportSessionIDReissue;
228                         if (idManager.InitializeRequest (context, false, out supportSessionIDReissue))
229                                 return; // Redirected, will come back here in a while
230                         string sessionId = idManager.GetSessionID (context);
231
232                         handler.InitializeRequest (context);
233
234                         storeData = GetStoreData (context, sessionId, isReadOnly);
235
236                         storeIsNew = false;
237                         if (storeData == null && !storeLocked) {
238                                 storeIsNew = true;
239                                 sessionId = idManager.CreateSessionID (context);
240                                 Trace.WriteLine ("New session ID allocated: " + sessionId);
241                                 bool redirected;
242                                 bool cookieAdded;
243                                 idManager.SaveSessionID (context, sessionId, out redirected, out cookieAdded);
244                                 if (redirected) {
245                                         if (supportSessionIDReissue)
246                                                 handler.CreateUninitializedItem (context, sessionId, (int)config.Timeout.TotalMinutes);
247                                         context.Response.End ();
248                                         return;
249                                 }
250                                 else
251                                         storeData = handler.CreateNewStoreData (context, (int)config.Timeout.TotalMinutes);
252                         }
253                         else if (storeData == null && storeLocked) {
254                                 WaitForStoreUnlock (context, sessionId, isReadOnly);
255                         }
256                         else if (storeData != null &&
257                                  !storeLocked &&
258                                  storeSessionAction == SessionStateActions.InitializeItem &&
259                                  IsCookieLess (context, config)) {
260                                 storeData = handler.CreateNewStoreData (context, (int)config.Timeout.TotalMinutes);
261                         }
262
263                         container = CreateContainer (sessionId, storeData, storeIsNew, isReadOnly);
264                         SessionStateUtility.AddHttpSessionStateToContext (app.Context, container);
265                         if (storeIsNew) {
266                                 OnSessionStart ();
267                                 HttpSessionState hss = app.Session;
268
269                                 if (hss != null)
270                                         storeData.Timeout = hss.Timeout;
271                         }
272
273                         // Whenever a container is abandoned, we temporarily disable the expire call back.
274                         // So in this case we are quite sure we have a brand new container, so we make sure it works again.
275                         supportsExpiration = handler.SetItemExpireCallback (OnSessionExpired);
276                 }
277
278                 void OnReleaseRequestState (object o, EventArgs args) {
279
280                         Trace.WriteLine ("SessionStateModule.OnReleaseRequestState (hash " + this.GetHashCode ().ToString ("x") + ")");
281
282                         HttpApplication application = (HttpApplication) o;
283                         HttpContext context = application.Context;
284                         if (!(context.Handler is IRequiresSessionState))
285                                 return;
286
287                         Trace.WriteLine ("\tsessionId == " + container.SessionID);
288                         Trace.WriteLine ("\trequest path == " + context.Request.FilePath);
289                         Trace.WriteLine ("\tHandler (" + context.Handler + ") requires session state");
290                         try {
291                                 if (!container.IsAbandoned) {
292                                         Trace.WriteLine ("\tnot abandoned");
293                                         if (!container.IsReadOnly) {
294                                                 Trace.WriteLine ("\tnot read only, storing and releasing");
295                                                 handler.SetAndReleaseItemExclusive (context, container.SessionID, storeData, storeLockId, storeIsNew);
296                                         }
297                                         else {
298                                                 Trace.WriteLine ("\tread only, releasing");
299                                                 handler.ReleaseItemExclusive (context, container.SessionID, storeLockId);
300                                         }
301                                         handler.ResetItemTimeout (context, container.SessionID);
302                                 }
303                                 else {
304                                         handler.ReleaseItemExclusive (context, container.SessionID, storeLockId);
305                                         handler.RemoveItem (context, container.SessionID, storeLockId, storeData);
306                                         if (supportsExpiration)
307 #if TARGET_J2EE
308                                                 ;
309                                         else
310 #else
311                                                 // Make sure the expiration handler is not called after we will have raised
312                                                 // the session end event.
313                                                 handler.SetItemExpireCallback (null);
314 #endif
315                                         SessionStateUtility.RaiseSessionEnd (container, this, args);
316                                 }
317                                 SessionStateUtility.RemoveHttpSessionStateFromContext (context);
318                         }
319                         finally {
320                                 container = null;
321                                 storeData = null;
322                         }
323                 }
324
325                 void OnEndRequest (object o, EventArgs args) {
326                         if (handler == null)
327                                 return;
328
329                         if (container != null)
330                                 OnReleaseRequestState (o, args);
331
332                         HttpApplication application = o as HttpApplication;
333                         if (application == null)
334                                 return;
335                         if (handler != null)
336                                 handler.EndRequest (application.Context);
337                 }
338
339                 SessionStateStoreData GetStoreData (HttpContext context, string sessionId, bool isReadOnly) {
340                         SessionStateStoreData item;
341                         item = (isReadOnly) ?
342                                 handler.GetItem (context,
343                                                                  sessionId,
344                                                                  out storeLocked,
345                                                                  out storeLockAge,
346                                                                  out storeLockId,
347                                                                  out storeSessionAction)
348                                                                  :
349                                 handler.GetItemExclusive (context,
350                                                                           sessionId,
351                                                                           out storeLocked,
352                                                                           out storeLockAge,
353                                                                           out storeLockId,
354                                                                           out storeSessionAction);
355                         if (storeLockId == null)
356                                 storeLockId = 0;
357
358                         return item;
359                 }
360
361                 void WaitForStoreUnlock (HttpContext context, string sessionId, bool isReadonly) {
362                         AutoResetEvent are = new AutoResetEvent (false);
363                         TimerCallback tc = new TimerCallback (StoreUnlockWaitCallback);
364                         CallbackState cs = new CallbackState (context, are, sessionId, isReadonly);
365                         using (Timer timer = new Timer (tc, cs, 500, 500)) {
366                                 try {
367                                         are.WaitOne (executionTimeout, false);
368                                 }
369                                 catch {
370                                         storeData = null;
371                                 }
372                         }
373                 }
374
375                 void StoreUnlockWaitCallback (object s) {
376                         CallbackState state = (CallbackState) s;
377
378                         SessionStateStoreData item = GetStoreData (state.Context, state.SessionId, state.IsReadOnly);
379
380                         if (item == null && storeLocked && (storeLockAge > executionTimeout)) {
381                                 handler.ReleaseItemExclusive (state.Context, state.SessionId, storeLockId);
382                                 storeData = null; // Create new state
383                                 state.AutoEvent.Set ();
384                         }
385                         else if (item != null && !storeLocked) {
386                                 storeData = item;
387                                 state.AutoEvent.Set ();
388                         }
389                 }
390
391                 HttpSessionStateContainer CreateContainer (string sessionId, SessionStateStoreData data, bool isNew, bool isReadOnly) {
392                         if (data == null)
393                                 return new HttpSessionStateContainer (
394                                         sessionId, null, null, 0, isNew,
395                                         config.Cookieless, config.Mode, isReadOnly);
396                         
397                         return new HttpSessionStateContainer (
398                                 sessionId,
399                                 data.Items,
400                                 data.StaticObjects,
401                                 data.Timeout,
402                                 isNew,
403                                 config.Cookieless,
404                                 config.Mode,
405                                 isReadOnly);
406                 }
407
408                 void OnSessionExpired (string id, SessionStateStoreData item) {
409                         SessionStateUtility.RaiseSessionEnd (
410                                 CreateContainer (id, item, false, true),
411                                 this, EventArgs.Empty);
412                 }
413
414                 void OnSessionStart () {
415                         EventHandler eh = events [startEvent] as EventHandler;
416                         if (eh != null)
417                                 eh (this, EventArgs.Empty);
418                 }
419         }
420 }
421 #endif