File: State\SessionStateItemCollection.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="SessionStateItemCollection.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
/*
 * SessionStateItemCollection
 *
 * Copyright (c) 1998-1999, Microsoft Corporation
 *
 */
 
namespace System.Web.SessionState {
 
    using System.IO;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Web.Util;
    using System.Security;
    using System.Security.Permissions;
    using System.Diagnostics.CodeAnalysis;
 
    public interface ISessionStateItemCollection : ICollection {
 
        Object this[String name]
        {
            get;
            set;
        }
 
        Object this[int index]
        {
            get;
            set;
        }
 
        void Remove(String name);
 
        void RemoveAt(int index);
 
        void Clear();
 
        NameObjectCollectionBase.KeysCollection Keys {
            get;
        }
 
        bool Dirty {
            get;
            set;
        }
    }
 
    public sealed class SessionStateItemCollection : NameObjectCollectionBase, ISessionStateItemCollection {
 
        class KeyedCollection : NameObjectCollectionBase {
 
            internal KeyedCollection(int count) : base(count, Misc.CaseInsensitiveInvariantKeyComparer) {
            }
 
            internal Object this[String name]
            {
                get {
                    return BaseGet(name);
                }
 
                set {
                    Object oldValue = BaseGet(name);
                    if (oldValue == null && value == null)
                        return;
 
                    BaseSet(name, value);
                }
            }
 
            internal Object this[int index]
            {
                get {
                    return BaseGet(index);
                }
            }
 
            internal void Remove(String name) {
                BaseRemove(name);
            }
 
            internal void RemoveAt(int index) {
                BaseRemoveAt(index);
            }
 
            internal void Clear() {
                BaseClear();
            }
 
            internal string GetKey(  int index) {
                return BaseGetKey(index);
            }
 
            internal bool ContainsKey(string name) {
                // Please note that we don't expect null value to be inserted.
                return (BaseGet(name) != null);
            }
        }
 
        class SerializedItemPosition {
            int _offset;
            int _dataLength;
 
            internal SerializedItemPosition(int offset, int dataLength) {
                this._offset = offset;
                this._dataLength = dataLength;
            }
 
            internal int Offset {
                get { return _offset; }
            }
 
            internal int DataLength {
                get { return _dataLength; }
            }
 
            // Mark the item as deserialized by making the offset -1.
            internal void MarkDeserializedOffset() {
                _offset = -1;
            }
 
            internal void MarkDeserializedOffsetAndCheck() {
                if (_offset >= 0) {
                    MarkDeserializedOffset();
                }
                else {
                    Debug.Fail("Offset shouldn't be negative inside MarkDeserializedOffsetAndCheck.");
                }
            }
 
            internal bool IsDeserialized {
                get { return _offset < 0; }
            }
        }
 
        static Hashtable s_immutableTypes;
        const int       NO_NULL_KEY = -1;
        const int       SIZE_OF_INT32 = 4;
        bool            _dirty;
        KeyedCollection _serializedItems;
        Stream          _stream;
        int             _iLastOffset;
        object          _serializedItemsLock = new object();
 
        public SessionStateItemCollection() : base(Misc.CaseInsensitiveInvariantKeyComparer) {
        }
 
        static SessionStateItemCollection() {
            Type t;
            s_immutableTypes = new Hashtable(19);
 
            t=typeof(String);
            s_immutableTypes.Add(t, t);
            t=typeof(Int32);
            s_immutableTypes.Add(t, t);
            t=typeof(Boolean);
            s_immutableTypes.Add(t, t);
            t=typeof(DateTime);
            s_immutableTypes.Add(t, t);
            t=typeof(Decimal);
            s_immutableTypes.Add(t, t);
            t=typeof(Byte);
            s_immutableTypes.Add(t, t);
            t=typeof(Char);
            s_immutableTypes.Add(t, t);
            t=typeof(Single);
            s_immutableTypes.Add(t, t);
            t=typeof(Double);
            s_immutableTypes.Add(t, t);
            t=typeof(SByte);
            s_immutableTypes.Add(t, t);
            t=typeof(Int16);
            s_immutableTypes.Add(t, t);
            t=typeof(Int64);
            s_immutableTypes.Add(t, t);
            t=typeof(UInt16);
            s_immutableTypes.Add(t, t);
            t=typeof(UInt32);
            s_immutableTypes.Add(t, t);
            t=typeof(UInt64);
            s_immutableTypes.Add(t, t);
            t=typeof(TimeSpan);
            s_immutableTypes.Add(t, t);
            t=typeof(Guid);
            s_immutableTypes.Add(t, t);
            t=typeof(IntPtr);
            s_immutableTypes.Add(t, t);
            t=typeof(UIntPtr);
            s_immutableTypes.Add(t, t);
        }
 
        static internal bool IsImmutable(Object o) {
            return s_immutableTypes[o.GetType()] != null;
        }
 
        internal void DeserializeAllItems() {
            if (_serializedItems == null) {
                return;
            }
 
            lock (_serializedItemsLock) {
                for (int i = 0; i < _serializedItems.Count; i++) {
                    DeserializeItem(_serializedItems.GetKey(i), false);
                }
            }
        }
 
        void DeserializeItem(int index) {
            // No-op if SessionStateItemCollection is not deserialized from a persistent storage.
            if (_serializedItems == null) {
                return;
            }
 
#if DBG
            // The keys in _serializedItems should match the beginning part of
            // the list in NameObjectCollectionBase
            for (int i=0; i < _serializedItems.Count; i++) {
                Debug.Assert(_serializedItems.GetKey(i) == BaseGetKey(i));
            }
#endif
 
            lock (_serializedItemsLock) {
                // No-op if the item isn't serialized.
                if (index >= _serializedItems.Count) {
                    return;
                }
 
                DeserializeItem(_serializedItems.GetKey(index), false);
            }
        }
 
        [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.SerializationFormatter)]
        private object ReadValueFromStreamWithAssert() {
            return AltSerialization.ReadValueFromStream(new BinaryReader(_stream));
        }
 
        void DeserializeItem(String name, bool check) {
            object          val;
 
            lock (_serializedItemsLock) {
                if (check) {
                    // No-op if SessionStateItemCollection is not deserialized from a persistent storage,
                    if (_serializedItems == null) {
                        return;
                    }
 
                    // User is asking for an item we don't have.
                    if (!_serializedItems.ContainsKey(name)) {
                        return;
                    }
                }
 
                Debug.Assert(_serializedItems != null);
                Debug.Assert(_stream != null);
 
                SerializedItemPosition position = (SerializedItemPosition)_serializedItems[name];
                if (position.IsDeserialized) {
                    // It has been deserialized already.
                    return;
                }
 
                // Position the stream to the place where the item is stored.
                _stream.Seek(position.Offset, SeekOrigin.Begin);
 
                // Set the value
                Debug.Trace("SessionStateItemCollection", "Deserialized an item: keyname=" + name);
 
                if (!HttpRuntime.DisableProcessRequestInApplicationTrust) {
                    // VSWhidbey 427316: Sandbox Serialization in non full trust cases
                    if (HttpRuntime.NamedPermissionSet != null && HttpRuntime.ProcessRequestInApplicationTrust) {
                        HttpRuntime.NamedPermissionSet.PermitOnly();
                    }
                }
 
                // This deserialization work used to be done in AcquireRequestState event when
                // there is no user code on the stack.
                // In whidbey we added this on-demand deserialization for performance reason.  However,
                // in medium and low trust cases the page doesn't have permission to do it.
                // So we have to assert the permission.
                // (See VSWhidbey 275003)
                val = ReadValueFromStreamWithAssert();
 
                BaseSet(name, val);
 
                // At the end, mark the item as deserialized by making the offset -1
                position.MarkDeserializedOffsetAndCheck();
            }
 
        }
 
        void MarkItemDeserialized(String name) {
            // No-op if SessionStateItemCollection is not deserialized from a persistent storage,
            if (_serializedItems == null) {
                return;
            }
 
            lock (_serializedItemsLock) {
                // If the serialized collection contains this key, mark it deserialized
                if (_serializedItems.ContainsKey(name)) {
                    // Mark the item as deserialized by making it -1.
                    ((SerializedItemPosition)_serializedItems[name]).MarkDeserializedOffset();
                }
            }
        }
 
        void MarkItemDeserialized(int index) {
            // No-op if SessionStateItemCollection is not deserialized from a persistent storage,
            if (_serializedItems == null) {
                return;
            }
 
#if DBG
            // The keys in _serializedItems should match the beginning part of
            // the list in NameObjectCollectionBase
            for (int i=0; i < _serializedItems.Count; i++) {
                Debug.Assert(_serializedItems.GetKey(i) == BaseGetKey(i));
            }
#endif
 
            lock (_serializedItemsLock) {
                // No-op if the item isn't serialized.
                if (index >= _serializedItems.Count) {
                    return;
                }
 
               ((SerializedItemPosition)_serializedItems[index]).MarkDeserializedOffset();
            }
        }
 
        public bool Dirty {
            get {return _dirty;}
            set {_dirty = value;}
        }
 
        public Object this[String name]
        {
            get {
                DeserializeItem(name, true);
 
                Object obj = BaseGet(name);
                if (obj != null) {
                    if (!IsImmutable(obj)) {
                        // If the item is immutable (e.g. an array), then the caller has the ability to change
                        // its content without calling our setter.  So we have to mark the collection
                        // as dirty.
                        Debug.Trace("SessionStateItemCollection", "Setting _dirty to true in get");
                        _dirty = true;
                    }
                }
 
                return obj;
            }
 
            set {
                MarkItemDeserialized(name);
                Debug.Trace("SessionStateItemCollection", "Setting _dirty to true in set");
                BaseSet(name, value);
                _dirty = true;
            }
        }
 
        public Object this[int index]
        {
            get {
                DeserializeItem(index);
 
                Object obj = BaseGet(index);
                if (obj != null) {
                    if (!IsImmutable(obj)) {
                        Debug.Trace("SessionStateItemCollection", "Setting _dirty to true in get");
                        _dirty = true;
                    }
                }
 
                return obj;
            }
 
            set {
                MarkItemDeserialized(index);
                Debug.Trace("SessionStateItemCollection", "Setting _dirty to true in set");
                BaseSet(index, value);
                _dirty = true;
            }
        }
 
 
        public void Remove(String name) {
            lock (_serializedItemsLock) {
                if (_serializedItems != null) {
                    _serializedItems.Remove(name);
                }
 
                BaseRemove(name);
                _dirty = true;
            }
        }
 
        public void RemoveAt(int index) {
            lock (_serializedItemsLock) {
                if (_serializedItems != null && index < _serializedItems.Count) {
                    _serializedItems.RemoveAt(index);
                }
 
                BaseRemoveAt(index);
                _dirty = true;
            }
        }
 
        public void Clear() {
            lock (_serializedItemsLock) {
                if (_serializedItems != null) {
                    _serializedItems.Clear();
                }
                BaseClear();
                _dirty = true;
            }
        }
 
        public override IEnumerator GetEnumerator() {
            // Have to deserialize all items; otherwise the enumerator won't
            // work because we'll keep on changing the collection during
            // individual item deserialization
            DeserializeAllItems();
 
            return base.GetEnumerator();
        }
 
        public override NameObjectCollectionBase.KeysCollection Keys {
            get {
                // Unfortunately, we have to deserialize all items first, because
                // Keys.GetEnumerator might be called and we have the same problem
                // as in GetEnumerator() above.
                DeserializeAllItems();
 
                return base.Keys;
            }
        }
 
        [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.SerializationFormatter)]
        private void WriteValueToStreamWithAssert(object value, BinaryWriter writer) {
            AltSerialization.WriteValueToStream(value, writer);
        }
 
        [SuppressMessage("Microsoft.Security", "CA2107:ReviewDenyAndPermitOnlyUsage",
           Justification = "Not a new FxCop warning suppression -- this proped up again because Serialize(BinaryWriter writer) function was changed Serialize(BinaryWriter writer, bool assertSerializationFormatterPermission)")]
        public void Serialize(BinaryWriter writer) {
            int     count;
            int     i;
            long    iOffsetStart;
            long    iValueStart;
            string  key;
            object  value;
            long    curPos;
            byte[]  buffer = null;
            Stream  baseStream = writer.BaseStream;
 
            if (!HttpRuntime.DisableProcessRequestInApplicationTrust) {
                // VSWhidbey 427316: Sandbox Serialization in non full trust cases
                if (HttpRuntime.NamedPermissionSet != null && HttpRuntime.ProcessRequestInApplicationTrust) {
                    HttpRuntime.NamedPermissionSet.PermitOnly();
                }
            }
 
            lock (_serializedItemsLock) {
                count = Count;
                writer.Write(count);
 
                if (count > 0) {
                    if (BaseGet(null) != null) {
                        // We have a value with a null key.  Find its index.
                        for (i = 0; i < count; i++) {
                            key = BaseGetKey(i);
                            if (key == null) {
                                writer.Write(i);
                                break;
                            }
                        }
 
                        Debug.Assert(i != count);
                    }
                    else {
                        writer.Write(NO_NULL_KEY);
                    }
 
                    // Write out all the keys.
                    for (i = 0; i < count; i++) {
                        key = BaseGetKey(i);
                        if (key != null) {
                            writer.Write(key);
                        }
                    }
 
                    // Next, allocate space to store the offset:
                    // - We won't store the offset of first item because it's always zero.
                    // - The offset of an item is counted from the beginning of serialized values
                    // - But we will store the offset of the first byte off the last item because
                    //   we need that to calculate the size of the last item.
                    iOffsetStart = baseStream.Position;
                    baseStream.Seek(SIZE_OF_INT32 * count, SeekOrigin.Current);
 
                    iValueStart = baseStream.Position;
 
                    for (i = 0; i < count; i++) {
                        // See if that item has not be deserialized yet.
                        if (_serializedItems != null &&
                            i < _serializedItems.Count &&
                            !((SerializedItemPosition)_serializedItems[i]).IsDeserialized) {
 
                            SerializedItemPosition position = (SerializedItemPosition)_serializedItems[i];
 
                            Debug.Assert(_stream != null);
 
                            // The item is read as serialized data from a store, and it's still
                            // serialized, meaning no one has referenced it.  Just copy
                            // the bytes over.
 
                            // Move the stream to the serialized data and copy it over to writer
                            _stream.Seek(position.Offset, SeekOrigin.Begin);
 
                            if (buffer == null || buffer.Length < position.DataLength) {
                                buffer = new Byte[position.DataLength];
                            }
#if DBG
                            int read =
#endif
                            _stream.Read(buffer, 0, position.DataLength);
#if DBG
                            Debug.Assert(read == position.DataLength);
#endif
 
                            baseStream.Write(buffer, 0, position.DataLength);
                        }
                        else {
                            value = BaseGet(i);
                            WriteValueToStreamWithAssert(value, writer);
                        }
 
                        curPos = baseStream.Position;
 
                        // Write the offset
                        baseStream.Seek(i * SIZE_OF_INT32 + iOffsetStart, SeekOrigin.Begin);
                        writer.Write((int)(curPos - iValueStart));
 
                        // Move back to current position
                        baseStream.Seek(curPos, SeekOrigin.Begin);
 
                        Debug.Trace("SessionStateItemCollection",
                            "Serialize: curPost=" + curPos + ", offset= " + (int)(curPos - iValueStart));
                    }
                }
#if DBG
                writer.Write((byte)0xff);
#endif
            }
        }
 
        public static SessionStateItemCollection Deserialize(BinaryReader reader) {
            SessionStateItemCollection   d = new SessionStateItemCollection();
            int                 count;
            int                 nullKey;
            String              key;
            int                 i;
            byte[]              buffer;
 
            count = reader.ReadInt32();
 
            if (count > 0) {
                nullKey = reader.ReadInt32();
 
                d._serializedItems = new KeyedCollection(count);
 
                // First, deserialize all the keys
                for (i = 0; i < count; i++) {
                    if (i == nullKey) {
                        key = null;
                    }
                    else {
                        key = reader.ReadString();
                    }
 
                    // Need to set them with null value first, so that
                    // the order of them items is correct.
                    d.BaseSet(key, null);
                }
 
                // Next, deserialize all the offsets
                // First offset will be 0, and the data length will be the first read offset
                int offset0 = reader.ReadInt32();
                d._serializedItems[d.BaseGetKey(0)] = new SerializedItemPosition(0, offset0);
 
                int offset1 = 0;
                for (i = 1; i < count; i++) {
                    offset1 = reader.ReadInt32();
                    d._serializedItems[d.BaseGetKey(i)] = new SerializedItemPosition(offset0, offset1 - offset0);
                    offset0 = offset1;
                }
 
                // 
                d._iLastOffset = offset0;
 
                Debug.Trace("SessionStateItemCollection",
                    "Deserialize: _iLastOffset= " + d._iLastOffset);
 
                // _iLastOffset is the first byte past the last item, which equals
                // the total length of all serialized data
                buffer = new byte[d._iLastOffset];
                int bytesRead = reader.BaseStream.Read(buffer, 0, d._iLastOffset);
                if (bytesRead != d._iLastOffset) {
                    throw new HttpException(SR.GetString(SR.Invalid_session_state));
                }
                d._stream = new MemoryStream(buffer);
            }
 
    #if DBG
            Debug.Assert(reader.ReadByte() == 0xff);
    #endif
 
            d._dirty = false;
 
            return d;
        }
    }
}