Implementation of the 2.0 session state model
[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
42 namespace System.Web.SessionState
43 {
44         class CallbackState
45         {
46                 public HttpContext Context;
47                 public AutoResetEvent AutoEvent;
48
49                 public CallbackState (HttpContext context, AutoResetEvent e)
50                 {
51                         this.Context = context;
52                         this.AutoEvent = e;
53                 }
54         }
55         
56         // CAS - no InheritanceDemand here as the class is sealed
57         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
58         public sealed class SessionStateModule : IHttpModule
59         {
60                 internal const string HeaderName = "AspFilterSessionId";
61                 internal const string CookielessFlagName = "_SessionIDManager_IsCookieLess";
62                 
63                 static object locker = new object ();
64                 
65                 internal static string CookieName {
66                         get {
67                                 config = GetConfig ();
68                                 if (config == null)
69                                         return null;
70                                 return config.CookieName;
71                         }
72                 }
73                                 
74 #if TARGET_J2EE         
75                 static private SessionStateSection config {
76                         get {
77                                 return (SessionStateSection) AppDomain.CurrentDomain.GetData ("SessionStateModule.config");
78                         }
79                         set {
80                                 AppDomain.CurrentDomain.SetData ("SessionStateModule.config", value);
81                         }
82                 }
83                 
84                 static private Type handlerType
85                 {
86                         get {
87                                 return (Type) AppDomain.CurrentDomain.GetData ("SessionStateModule.handlerType");
88                         }
89                         set {
90                                 AppDomain.CurrentDomain.SetData ("SessionStateModule.handlerType", value);
91                         }
92                 }
93                 
94                 static private Type idManagerType
95                 {
96                         get {
97                                 return (Type) AppDomain.CurrentDomain.GetData ("SessionStateModule.idManagerType");
98                         }
99                         set {
100                                 AppDomain.CurrentDomain.SetData ("SessionStateModule.idManagerType", value);
101                         }
102                 }
103 #else
104                 static SessionStateSection config;
105                 static Type handlerType;
106                 static Type idManagerType;
107 #endif          
108                 SessionStateStoreProviderBase handler;
109                 ISessionIDManager idManager;
110                 HttpApplication app;
111                 
112                 // Store state
113                 bool storeLocked;
114                 TimeSpan storeLockAge;
115                 object storeLockId = new object();
116                 SessionStateActions storeSessionAction;
117                 SessionStateStoreData storeData;
118
119                 // Session state
120                 bool isReadOnly;
121                 bool isNew;
122                 bool supportSessionIDReissue;
123                 bool supportsExpiration;
124                 string sessionId;
125                 HttpSessionStateContainer container;
126                 
127                 // config
128                 static TimeSpan executionTimeout;
129                 static int executionTimeoutMS;
130
131                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
132                 public SessionStateModule ()
133                 {
134                 }
135
136                 public void Dispose ()
137                 {
138                     if (handler!=null)
139                         handler.Dispose();
140                 }
141
142                 static SessionStateSection GetConfig ()
143                 {
144                         lock (locker) {
145                                 if (config != null)
146                                         return config;
147
148                                 config = (SessionStateSection) WebConfigurationManager.GetSection ("system.web/sessionState");
149                                 SessionStateMode handlerMode = config.Mode;
150                                 
151 #if TARGET_J2EE
152                                 if (handlerMode == SessionStateMode.SQLServer || handlerMode == SessionStateMode.StateServer)
153                                         throw new NotImplementedException("You must use web.xml to specify session state handling");
154 #endif
155                                 InitTypesFromConfig (config, handlerMode);
156                                 HttpRuntimeSection runtime = WebConfigurationManager.GetSection ("system.web/httpruntime") as HttpRuntimeSection;
157                                 if (runtime != null) {
158                                         executionTimeout = runtime.ExecutionTimeout;
159                                         executionTimeoutMS = executionTimeout.Milliseconds;
160                                 }
161                                 
162                                 return config;
163                         }
164                 }
165
166                 static void InitTypesFromConfig (SessionStateSection config, SessionStateMode handlerMode)
167                 {
168                         if (handlerMode == SessionStateMode.StateServer)
169                                 handlerType = typeof (SessionStateServerHandler);
170
171 //                      if (handlerMode == SessionStateMode.SQLServer)
172 //                              handlerType = typeof (SessionSQLServerHandler);
173                         
174                         if (handlerMode == SessionStateMode.InProc)
175                                 handlerType = typeof (SessionInProcHandler);
176
177                         if (handlerMode == SessionStateMode.Custom)
178                                 handlerType = GetCustomHandlerType (config);
179                         
180                         try {
181                                 idManagerType = Type.GetType (config.SessionIDManagerType, true);
182                         } catch {
183                                 idManagerType = typeof (SessionIDManager);
184                         } 
185                 }
186
187                 static Type GetCustomHandlerType (SessionStateSection config)
188                 {
189                         return null;
190                 }
191                 
192                 [EnvironmentPermission (SecurityAction.Assert, Read = "MONO_XSP_STATIC_SESSION")]
193                 public void Init (HttpApplication app)
194                 {
195                         SessionStateSection cfg = GetConfig ();
196                         this.app = app;
197                         if (handlerType == null || idManagerType == null)
198                                 throw new HttpException ("Cannot initialize the session state module. Missing handler or ID manager types.");
199                         app.BeginRequest += new EventHandler (OnBeginRequest);
200                         app.AcquireRequestState += new EventHandler (OnAcquireRequestState);
201                         app.ReleaseRequestState += new EventHandler (OnReleaseRequestState);
202                         app.EndRequest += new EventHandler (OnEndRequest);
203
204                         if (handler == null) {
205                                 try {
206                                         handler = Activator.CreateInstance (handlerType, new object [] {cfg}) as SessionStateStoreProviderBase;
207                                         handler.Initialize (GetHandlerName (), GetHandlerConfig ());
208                                 } catch (Exception ex) {
209                                         throw new HttpException ("Failed to initialize session storage provider.", ex);
210                                 }
211                         }
212
213                         if (idManager == null) {
214                                 try {
215                                         idManager = Activator.CreateInstance (idManagerType) as ISessionIDManager;
216                                         idManager.Initialize ();
217                                 } catch (Exception ex) {
218                                         throw new HttpException ("Failed to initialize session ID manager.", ex);
219                                 }
220                         }
221                 }
222
223                 string GetHandlerName ()
224                 {
225                         switch (config.Mode) {
226                                 case SessionStateMode.InProc:
227                                 case SessionStateMode.StateServer:
228                                 case SessionStateMode.SQLServer:
229                                         return null; // set by the handler
230
231                                 case SessionStateMode.Custom:
232                                         return "Custom Session State Handler";
233
234                                 default:
235                                         throw new HttpException ("Unknown session handler mode.");
236                         }
237                 }
238
239                 NameValueCollection GetHandlerConfig ()
240                 {
241                         switch (config.Mode) {
242                                 case SessionStateMode.InProc:
243                                 case SessionStateMode.StateServer:
244                                 case SessionStateMode.SQLServer:
245                                         return new NameValueCollection ();
246
247                                 // TODO: implement
248                                 case SessionStateMode.Custom:
249                                         return new NameValueCollection ();
250
251                                 default:
252                                         throw new HttpException ("Unknown session handler mode.");
253                         }
254                 }
255
256                 internal static bool IsCookieLess (HttpContext context)
257                 {
258                         if (config.Cookieless == HttpCookieMode.UseCookies)
259                                 return false;
260                         if (config.Cookieless == HttpCookieMode.UseUri)
261                                 return true;
262                         object cookieless = context.Items [CookielessFlagName];
263                         if (cookieless == null)
264                                 return false;
265                         return (bool)cookieless;
266                 }
267                 
268                 void OnBeginRequest (object o, EventArgs args)
269                 {
270                         HttpApplication application = (HttpApplication) o;
271                         HttpContext context = application.Context;
272                         string base_path = context.Request.BaseVirtualDir;
273                         string id = UrlUtils.GetSessionId (base_path);
274
275                         if (id == null)
276                                 return;
277                         
278                         string new_path = UrlUtils.RemoveSessionId (base_path, context.Request.FilePath);
279                         context.Request.SetFilePath (new_path);
280                         context.Request.SetHeader (HeaderName, id);
281                         context.Response.SetAppPathModifier (String.Concat ("(", id, ")"));
282                 }
283
284                 void OnAcquireRequestState (object o, EventArgs args)
285                 {
286                         Console.WriteLine ("SessionStateModule.OnAcquireRequestState (hash {0})", this.GetHashCode ().ToString ("x"));
287                         HttpApplication application = (HttpApplication) o;
288                         HttpContext context = application.Context;
289
290                         if (!(context.Handler is IRequiresSessionState)) {
291                                 Console.WriteLine ("Handler ({0}) does not require session state", context.Handler);
292                                 return;
293                         }
294                         isReadOnly = (context.Handler is IReadOnlySessionState);                        
295                         
296                         if (idManager != null) {
297                                 if (idManager.InitializeRequest (context, false, out supportSessionIDReissue))
298                                         return; // Redirected, will come back here in a while
299                                 sessionId = idManager.GetSessionID (context);
300                         }
301                         
302                         if (handler != null) {
303                                 handler.InitializeRequest (context);
304                                 GetStoreData (context);
305                                 if (storeData == null && !storeLocked) {
306                                         isNew = true;
307                                         sessionId = idManager.CreateSessionID (context);
308                                         Console.WriteLine ("New session ID allocated: {0}", sessionId);
309                                         bool redirected = false;
310                                         bool cookieAdded = false;
311                                         idManager.SaveSessionID (context, sessionId, out redirected, out cookieAdded);
312                                         if (redirected) {
313                                                 if (supportSessionIDReissue)
314                                                         handler.CreateUninitializedItem (context, sessionId, config.Timeout.Minutes);
315                                                 context.Response.End();
316                                                 return;
317                                         } else
318                                                 storeData = handler.CreateNewStoreData (context, config.Timeout.Minutes);
319                                 } else if (storeData == null && storeLocked) {
320                                         WaitForStoreUnlock (context);
321                                 } else if (storeData != null &&
322                                            !storeLocked &&
323                                            storeSessionAction == SessionStateActions.InitializeItem &&
324                                            IsCookieLess (context)) {
325                                         storeData = handler.CreateNewStoreData (context, config.Timeout.Minutes);
326                                 }
327                                 
328                                 SessionSetup (context, isNew);
329                         }
330                 }
331                 
332                 void OnReleaseRequestState (object o, EventArgs args)
333                 {
334                         Console.WriteLine ("SessionStateModule.OnReleaseRequestState (hash {0})", this.GetHashCode ().ToString ("x"));
335                         Console.WriteLine ("\tsessionId == {0}", sessionId);
336                         if (handler == null)
337                                 return;
338
339                         HttpApplication application = (HttpApplication) o;
340                         HttpContext context = application.Context;
341                         if (!(context.Handler is IRequiresSessionState))
342                                 return;
343                         Console.WriteLine ("\trequest path == {0}", context.Request.FilePath);
344                         Console.WriteLine ("\tHandler ({0}) requires session state", context.Handler);
345                         
346                         if (!container.IsAbandoned) {
347                                 Console.WriteLine ("\tnot abandoned");
348                                 if (!isReadOnly) {
349                                         Console.WriteLine ("\tnot read only, storing and releasing");
350                                         handler.SetAndReleaseItemExclusive (context, sessionId, storeData, storeLockId, false);
351                                 } else {
352                                         Console.WriteLine ("\tread only, releasing");
353                                         handler.ReleaseItemExclusive (context, sessionId, storeLockId);
354                                 }
355                                 handler.ResetItemTimeout (context, sessionId);
356                         } else {
357                                 handler.ReleaseItemExclusive (context, sessionId, storeLockId);
358                                 handler.RemoveItem (context, sessionId, storeLockId, storeData);
359                         }
360                         SessionStateUtility.RemoveHttpSessionStateFromContext (context);
361                         if (supportsExpiration)
362                                 SessionStateUtility.RaiseSessionEnd (container, o, args);
363                 }
364
365                 void OnEndRequest (object o, EventArgs args)
366                 {
367                         if (handler == null)
368                                 return;
369                         
370                         HttpApplication application = o as HttpApplication;
371                         if (application == null)
372                                 return;
373                         if (handler != null)
374                                 handler.EndRequest (application.Context);
375                 }
376
377                 void GetStoreData (HttpContext context)
378                 {
379                         if (sessionId == null)
380                                 return;
381                         
382                         if (isReadOnly)
383                                 storeData = handler.GetItem (context,
384                                                              sessionId,
385                                                              out storeLocked,
386                                                              out storeLockAge,
387                                                              out storeLockId,
388                                                              out storeSessionAction);
389                         else
390                                 storeData = handler.GetItemExclusive (context,
391                                                                       sessionId,
392                                                                       out storeLocked,
393                                                                       out storeLockAge,
394                                                                       out storeLockId,
395                                                                       out storeSessionAction);
396                 }
397                 
398                 void WaitForStoreUnlock (HttpContext context)
399                 {
400                         AutoResetEvent are = new AutoResetEvent (false);
401                         TimerCallback tc = new TimerCallback (this.StoreUnlockWaitCallback);
402                         CallbackState cs = new CallbackState (context, are);
403                         using (Timer timer = new Timer (tc, cs, 500, 500)) {
404                                 try {
405                                         are.WaitOne (executionTimeout, false);
406                                 } catch {
407                                         storeData = null;
408                                 }
409                         }
410                 }
411
412                 void StoreUnlockWaitCallback (object s)
413                 {
414                         CallbackState state = s as CallbackState;
415                         GetStoreData (state.Context);
416                         if (storeData == null && storeLocked && (storeLockAge > executionTimeout)) {
417                                 handler.ReleaseItemExclusive (state.Context, sessionId, storeLockId);
418                                 state.AutoEvent.Set ();
419                         } else if (storeData != null && !storeLocked)
420                                 state.AutoEvent.Set ();
421                 }
422                 
423                 void SessionSetup (HttpContext context, bool isNew)
424                 {
425                         if (storeData != null && sessionId != null) {
426                                 container = new HttpSessionStateContainer (
427                                         sessionId,
428                                         storeData.Items,
429                                         storeData.StaticObjects,
430                                         storeData.Timeout,
431                                         isNew,
432                                         config.Cookieless,
433                                         config.Mode,
434                                         isReadOnly);
435                                 SessionStateUtility.AddHttpSessionStateToContext (context, container);
436                                 if (isNew) {
437                                         supportsExpiration = handler.SetItemExpireCallback (OnSessionExpired);
438                                         OnSessionStart ();
439                                 }
440                         }
441                 }
442
443                 void OnSessionExpired (string id, SessionStateStoreData item)
444                 {
445                 }
446                 
447                 void OnSessionStart ()
448                 {
449                         if (Start != null)
450                                 Start (this, EventArgs.Empty);
451                 }
452                 
453                 public event EventHandler Start;
454
455                 // This event is public, but only Session_[On]End in global.asax will be invoked if present.
456                 public event EventHandler End;
457         }
458 }
459 #endif