e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
629 lines
21 KiB
C#
629 lines
21 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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;
|
|
}
|
|
}
|
|
}
|