// // System.Web.SessionState.SesionStateModule // // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) // Stefan Görling (stefan@gorling.se) // Jackson Harper (jackson@ximian.com) // Marek Habersack (grendello@gmail.com) // // Copyright (C) 2002-2006 Novell, Inc (http://www.novell.com) // (C) 2003 Stefan Görling (http://www.gorling.se) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System.Collections.Specialized; using System.ComponentModel; using System.Web.Configuration; using System.Web.Caching; using System.Web.Util; using System.Security.Permissions; using System.Threading; using System.Configuration; using System.Diagnostics; namespace System.Web.SessionState { // CAS - no InheritanceDemand here as the class is sealed [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)] public sealed class SessionStateModule : IHttpModule { internal const string HeaderName = "AspFilterSessionId"; internal const string CookielessFlagName = "_SessionIDManager_IsCookieLess"; static readonly object startEvent = new object (); static readonly object endEvent = new object (); SessionStateSection config; SessionStateStoreProviderBase handler; ISessionIDManager idManager; bool supportsExpiration; HttpApplication app; // Store state bool storeLocked; TimeSpan storeLockAge; object storeLockId; SessionStateActions storeSessionAction; bool storeIsNew; // Session state SessionStateStoreData storeData; HttpSessionStateContainer container; // config TimeSpan executionTimeout; //int executionTimeoutMS; EventHandlerList events = new EventHandlerList (); public event EventHandler Start { add { events.AddHandler (startEvent, value); } remove { events.RemoveHandler (startEvent, value); } } // This event is public, but only Session_[On]End in global.asax will be invoked if present. public event EventHandler End { add { events.AddHandler (endEvent, value); } remove { events.RemoveHandler (endEvent, value); } } [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)] public SessionStateModule () { } public void Dispose () { app.BeginRequest -= new EventHandler (OnBeginRequest); app.AcquireRequestState -= new EventHandler (OnAcquireRequestState); app.ReleaseRequestState -= new EventHandler (OnReleaseRequestState); app.EndRequest -= new EventHandler (OnEndRequest); handler.Dispose (); } [EnvironmentPermission (SecurityAction.Assert, Read = "MONO_XSP_STATIC_SESSION")] public void Init (HttpApplication app) { config = (SessionStateSection) WebConfigurationManager.GetSection ("system.web/sessionState"); ProviderSettings settings; switch (config.Mode) { case SessionStateMode.Custom: settings = config.Providers [config.CustomProvider]; if (settings == null) throw new HttpException (String.Format ("Cannot find '{0}' provider.", config.CustomProvider)); break; case SessionStateMode.Off: return; case SessionStateMode.InProc: settings = new ProviderSettings (null, typeof (SessionInProcHandler).AssemblyQualifiedName); break; case SessionStateMode.SQLServer: settings = new ProviderSettings (null, typeof (SessionSQLServerHandler).AssemblyQualifiedName); break; case SessionStateMode.StateServer: settings = new ProviderSettings (null, typeof (SessionStateServerHandler).AssemblyQualifiedName); break; default: throw new NotImplementedException (String.Format ("The mode '{0}' is not implemented.", config.Mode)); } handler = (SessionStateStoreProviderBase) ProvidersHelper.InstantiateProvider (settings, typeof (SessionStateStoreProviderBase)); if (String.IsNullOrEmpty(config.SessionIDManagerType)) { idManager = new SessionIDManager (); } else { Type idManagerType = HttpApplication.LoadType (config.SessionIDManagerType, true); idManager = (ISessionIDManager)Activator.CreateInstance (idManagerType); } try { idManager.Initialize (); } catch (Exception ex) { throw new HttpException ("Failed to initialize session ID manager.", ex); } supportsExpiration = handler.SetItemExpireCallback (OnSessionExpired); HttpRuntimeSection runtime = HttpRuntime.Section; executionTimeout = runtime.ExecutionTimeout; //executionTimeoutMS = executionTimeout.Milliseconds; this.app = app; app.BeginRequest += new EventHandler (OnBeginRequest); app.AcquireRequestState += new EventHandler (OnAcquireRequestState); app.ReleaseRequestState += new EventHandler (OnReleaseRequestState); app.EndRequest += new EventHandler (OnEndRequest); } internal static bool IsCookieLess (HttpContext context, SessionStateSection config) { if (config.Cookieless == HttpCookieMode.UseCookies) return false; if (config.Cookieless == HttpCookieMode.UseUri) return true; object cookieless = context.Items [CookielessFlagName]; if (cookieless == null) return false; return (bool) cookieless; } void OnBeginRequest (object o, EventArgs args) { HttpApplication application = (HttpApplication) o; HttpContext context = application.Context; string file_path = context.Request.FilePath; string base_path = VirtualPathUtility.GetDirectory (file_path); string id = UrlUtils.GetSessionId (base_path); if (id == null) return; string new_path = UrlUtils.RemoveSessionId (base_path, file_path); context.Request.SetFilePath (new_path); context.Request.SetHeader (HeaderName, id); context.Response.SetAppPathModifier (id); } void OnAcquireRequestState (object o, EventArgs args) { Trace.WriteLine ("SessionStateModule.OnAcquireRequestState (hash " + this.GetHashCode ().ToString ("x") + ")"); HttpApplication application = (HttpApplication) o; HttpContext context = application.Context; if (!(context.Handler is IRequiresSessionState)) { Trace.WriteLine ("Handler (" + context.Handler + ") does not require session state"); return; } bool isReadOnly = (context.Handler is IReadOnlySessionState); bool supportSessionIDReissue; if (idManager.InitializeRequest (context, false, out supportSessionIDReissue)) return; // Redirected, will come back here in a while string sessionId = idManager.GetSessionID (context); handler.InitializeRequest (context); storeData = GetStoreData (context, sessionId, isReadOnly); storeIsNew = false; if (storeData == null && !storeLocked) { storeIsNew = true; sessionId = idManager.CreateSessionID (context); Trace.WriteLine ("New session ID allocated: " + sessionId); bool redirected; bool cookieAdded; idManager.SaveSessionID (context, sessionId, out redirected, out cookieAdded); if (redirected) { if (supportSessionIDReissue) handler.CreateUninitializedItem (context, sessionId, (int)config.Timeout.TotalMinutes); context.Response.End (); return; } else storeData = handler.CreateNewStoreData (context, (int)config.Timeout.TotalMinutes); } else if (storeData == null && storeLocked) { WaitForStoreUnlock (context, sessionId, isReadOnly); } else if (storeData != null && !storeLocked && storeSessionAction == SessionStateActions.InitializeItem && IsCookieLess (context, config)) { storeData = handler.CreateNewStoreData (context, (int)config.Timeout.TotalMinutes); } container = CreateContainer (sessionId, storeData, storeIsNew, isReadOnly); SessionStateUtility.AddHttpSessionStateToContext (app.Context, container); if (storeIsNew) { OnSessionStart (); HttpSessionState hss = app.Session; if (hss != null) storeData.Timeout = hss.Timeout; } // Whenever a container is abandoned, we temporarily disable the expire call back. // So in this case we are quite sure we have a brand new container, so we make sure it works again. supportsExpiration = handler.SetItemExpireCallback (OnSessionExpired); } void OnReleaseRequestState (object o, EventArgs args) { Trace.WriteLine ("SessionStateModule.OnReleaseRequestState (hash " + this.GetHashCode ().ToString ("x") + ")"); HttpApplication application = (HttpApplication) o; HttpContext context = application.Context; if (!(context.Handler is IRequiresSessionState)) return; Trace.WriteLine ("\tsessionId == " + container.SessionID); Trace.WriteLine ("\trequest path == " + context.Request.FilePath); Trace.WriteLine ("\tHandler (" + context.Handler + ") requires session state"); try { if (!container.IsAbandoned) { Trace.WriteLine ("\tnot abandoned"); if (!container.IsReadOnly) { Trace.WriteLine ("\tnot read only, storing and releasing"); handler.SetAndReleaseItemExclusive (context, container.SessionID, storeData, storeLockId, storeIsNew); } else { Trace.WriteLine ("\tread only, releasing"); handler.ReleaseItemExclusive (context, container.SessionID, storeLockId); } handler.ResetItemTimeout (context, container.SessionID); } else { handler.RemoveItem (context, container.SessionID, storeLockId, storeData); handler.ReleaseItemExclusive (context, container.SessionID, storeLockId); if (supportsExpiration) // Make sure the expiration handler is not called after we will have raised // the session end event. handler.SetItemExpireCallback (null); SessionStateUtility.RaiseSessionEnd (container, this, args); } SessionStateUtility.RemoveHttpSessionStateFromContext (context); } finally { container = null; storeData = null; } } void OnEndRequest (object o, EventArgs args) { if (handler == null) return; if (container != null) OnReleaseRequestState (o, args); HttpApplication application = o as HttpApplication; if (application == null) return; if (handler != null) handler.EndRequest (application.Context); } SessionStateStoreData GetStoreData (HttpContext context, string sessionId, bool isReadOnly) { SessionStateStoreData item; item = (isReadOnly) ? handler.GetItem (context, sessionId, out storeLocked, out storeLockAge, out storeLockId, out storeSessionAction) : handler.GetItemExclusive (context, sessionId, out storeLocked, out storeLockAge, out storeLockId, out storeSessionAction); if (storeLockId == null) storeLockId = 0; return item; } void WaitForStoreUnlock (HttpContext context, string sessionId, bool isReadOnly) { DateTime dt = DateTime.Now; while ((DateTime.Now - dt) < executionTimeout) { Thread.Sleep(500); storeData = GetStoreData (context, sessionId, isReadOnly); if (storeData == null && storeLocked && (storeLockAge > executionTimeout)) { handler.ReleaseItemExclusive (context, sessionId, storeLockId); return; } else if (storeData != null && !storeLocked) { //we have the session return; } } } HttpSessionStateContainer CreateContainer (string sessionId, SessionStateStoreData data, bool isNew, bool isReadOnly) { if (data == null) return new HttpSessionStateContainer ( sessionId, null, null, 0, isNew, config.Cookieless, config.Mode, isReadOnly); return new HttpSessionStateContainer ( sessionId, data.Items, data.StaticObjects, data.Timeout, isNew, config.Cookieless, config.Mode, isReadOnly); } void OnSessionExpired (string id, SessionStateStoreData item) { SessionStateUtility.RaiseSessionEnd ( CreateContainer (id, item, false, true), this, EventArgs.Empty); } void OnSessionStart () { EventHandler eh = events [startEvent] as EventHandler; if (eh != null) eh (this, EventArgs.Empty); } } }