File: src\Framework\MS\Internal\DataStreams.cs
Project: wpf\PresentationFramework.csproj (PresentationFramework)
//---------------------------------------------------------------------------
//
// File: DataStreams.cs
//
// Description:
// Holds the data for Avalon BindProducts in the journal
//
//
// History:
//  7/30/2001:  t-ypchen        Created
//  5/20/2003:  kusumav         Ported from to WCP_Dev
// 11/14/2005:  Microsoft         Refactoring as part of the "Island Frame" implementation; 
//                              introduced the internal IJournalState interface.
//
// Copyright (C) 2003 by Microsoft Corporation.  All rights reserved.
// 
//---------------------------------------------------------------------------
 
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Permissions;
using MS.Internal.AppModel;
using System.Windows;
using System.Windows.Navigation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
 
namespace MS.Internal.AppModel
{
    #region SubStream struct
    [Serializable]
    internal struct SubStream
    {
        internal SubStream(string propertyName, byte[] dataBytes)
        {
            _propertyName = propertyName;
            _data = dataBytes;
        }
 
        internal string _propertyName;
        internal byte[] _data;
    }
    #endregion SubStream struct
    
    #region DataStreams class
    [Serializable]
    internal class DataStreams
    {
        /// <SecurityNote>
        /// Critical - as the constructor initializes the Critical member _subStreams.
        /// Safe - as the initialization is to a safe value.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        internal DataStreams()
        {
            // Dummy constructor to keep FxCop Critical rules happy.
        }
 
        /// <SecurityNote>
        /// Critical - as this refers to Critical member _subStreams.
        /// Safe - as this doesn't expose the data
        /// </SecurityNote>
        internal bool HasAnyData
        {
            [SecurityCritical, SecurityTreatAsSafe]
            get 
            {
                return _subStreams != null && _subStreams.Count > 0
                    || _customJournaledObjects != null && _customJournaledObjects.Count > 0;
            }
        }
 
        /// <SecurityNote>
        /// Critical - as this refers to Critical member _subStreams.
        /// Safe - as this doesn't expose the data.  Returning if a stream exists
        ///        is not Critical information, the content of the stream is.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private bool HasSubStreams(object key)
        {
            return _subStreams != null && _subStreams.Contains(key);
        }
 
        /// <SecurityNote>
        /// Critical - as this returns Critical data in _subStreams.
        /// </SecurityNote>
        [SecurityCritical]
        private ArrayList GetSubStreams(object key)
        {
            ArrayList subStreams = (ArrayList) _subStreams[key];
            if (subStreams == null)
            {
                subStreams = new ArrayList(3);
                _subStreams[key] = subStreams;
            }
 
            return subStreams;
        }
 
        private delegate void NodeOperation(object node);
 
        /// <summary>
        /// Build an ArrayList of SubStreams where each SubStream represents one of the
        /// node's journalable DPs.
        /// </summary>
        /// <param name="element"></param>
        /// <returns>The ArrayList of SubStreams. May be null.</returns>
        /// <SecurityNote>
        /// Critical - as this calls Formatter.Serialize under an elevation and 
        ///            returns the value so obtained.
        /// </SecurityNote>
        [SecurityCritical]
        private ArrayList SaveSubStreams(UIElement element)
        {
            ArrayList subStreams = null;
 
#pragma warning disable 618
            if ((element != null) && (element.PersistId != 0))
#pragma warning restore 618
            {
                LocalValueEnumerator dpEnumerator = element.GetLocalValueEnumerator();
 
                while (dpEnumerator.MoveNext())
                {
                    LocalValueEntry localValueEntry = (LocalValueEntry)dpEnumerator.Current;
                    FrameworkPropertyMetadata metadata = localValueEntry.Property.GetMetadata(element.DependencyObjectType) as FrameworkPropertyMetadata;
                                        
                    if (metadata == null)
                    {
                        continue;
                    }
 
                    // To be saved, a DP should have the correct metadata and NOT be an expression or data bound.
                    // Since Bind inherits from Expression, the test for Expression will suffice.
                    // NOTE: we do not journal expression. So we should let parser restore it in BamlRecordReader.SetDependencyValue.
                    // Please see Windows OS bug # 1852349 for details.
                    if (metadata.Journal && (!(localValueEntry.Value is Expression)))
                    {
                        // These properties should not be journaled.
                        // 
 
                        if (object.ReferenceEquals(localValueEntry.Property, Frame.SourceProperty))
                            continue;
 
                        if (subStreams == null)
                        {
                            subStreams = new ArrayList(3);
                        }
 
                        object currentValue = element.GetValue(localValueEntry.Property);
                        byte[] bytes = null;
 
                        if ((currentValue != null) && !(currentValue is Uri))
                        {
                            // Convert the value of the DP into a byte array
                            MemoryStream byteStream = new MemoryStream();
                            new SecurityPermission(SecurityPermissionFlag.SerializationFormatter).Assert();
                            try
                            {
                                this.Formatter.Serialize(byteStream, currentValue);
                            }
                            finally
                            {
                                SecurityPermission.RevertAssert();
                            }
                            
                            bytes = byteStream.ToArray();
                            // Dispose the stream
                            ((IDisposable)byteStream).Dispose( );
                        }
 
                        // Save the byte array by the property name
                        subStreams.Add(new SubStream(localValueEntry.Property.Name, bytes));
                    }
                }
            }
 
            return subStreams;
        }
 
        /// <SecurityNote>
        /// Critical - as this invokes Critical function SaveSubStreams and sets data
        ///            in Critical member _subStreams.
        /// Safe - as this doesn't return the Critical data, so any private members that
        ///        are saved from Serializing the object are protected.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private void SaveState(object node)
        {
            UIElement element = node as UIElement;
            if (element == null)
            {
                return;
            }
 
            // Due to bug 1282529, PersistId can be null. Only XAML/BAML-loaded elements have it.
            // Besides for PageFunctions journaled by type, the PersistId check below is needed
            // because elements might have been added to the tree after loading from XAML/BAML.
#pragma warning disable 618
            int persistId = element.PersistId;
#pragma warning restore 618
 
            if (persistId != 0)
            {
                ArrayList subStreams = this.SaveSubStreams(element);
                if (subStreams != null)
                {
                    //
                    // If one element in the tree is replaced with a new element which is created
                    // from a xaml/baml stream programatically, this new element and all its descendent nodes 
                    // would have another set of PersistId starting from 1, it would end up with two or more 
                    // elements in the same page share the same PersistId.  We cannot guarantee to restore 
                    // journaldata for the newly added elements when doing the journaling navigation.
                    //
                    // So to do some level protection to avoid application crash, the code doesn't update the 
                    // journaldata for that element id if the data was set already.
                    //
                    // 
 
 
 
                    if (!_subStreams.Contains(persistId))
                    {
                        _subStreams[persistId] = subStreams;
                    }
                }
 
                IJournalState customJournalingObject = node as IJournalState;
                if (customJournalingObject != null)
                {
                    object customState = customJournalingObject.GetJournalState(JournalReason.NewContentNavigation);
                    if (customState != null)
                    {
                        if (_customJournaledObjects == null)
                        {
                            _customJournaledObjects = new HybridDictionary(2);
                        }
 
                        //
                        // Again, We cannot guarantee the PeristId of all elements in the same page are unique.
                        // Some IJouralState aware node such as Frame, FlowDocumentPageViewer could be added 
                        // programatically after the page is created from baml stream,  the new added node could also 
                        // be created from baml/xaml stream by Parser.
                        //
                        if (!_customJournaledObjects.Contains(persistId))
                        {
                            _customJournaledObjects[persistId] = customState;
                        }
                    }
                }
            }
        }
 
        internal void PrepareForSerialization()
        {
            if (_customJournaledObjects != null)
            {
                foreach (DictionaryEntry entry in _customJournaledObjects)
                {
                    CustomJournalStateInternal cjs = (CustomJournalStateInternal)entry.Value;
                    cjs.PrepareForSerialization();
                }
            }
 
            // Everything in _subStreams is already binary-serialized.
        }
 
        /// <SecurityNote>
        /// Critical - as this invokes Deserialize under an elevation on a random stream passed in.
        /// Safe - Demands SerializationFormatter permissions
        /// </SecurityNote>
        [SecuritySafeCritical]
        private void LoadSubStreams(UIElement element, ArrayList subStreams)
        {
            for (int subStreamIndex = 0; subStreamIndex < subStreams.Count; ++subStreamIndex)
            {
                SubStream subStream = (SubStream)subStreams[subStreamIndex];
 
                // Restore the value of an individual DP
                DependencyProperty dp = DependencyProperty.FromName(subStream._propertyName, element.GetType());
                // If the dp cannot be found it may mean that we navigated back to a loose file that has been changed.
                if (dp != null)
                {
                    object newValue = null;
                    if (subStream._data != null)
                    {
                        try
                        {
	                        new SecurityPermission(SecurityPermissionFlag.SerializationFormatter).Demand(); // prevent any journal metadata de-serialization in partial trust
                            newValue = this.Formatter.Deserialize(new MemoryStream(subStream._data));
                        }
                        catch (SecurityException)
                        {
                            newValue = DependencyProperty.UnsetValue;
                        }
                    }
                    element.SetValue(dp, newValue);
                }
            }
        }
 
        /// <SecurityNote>
        /// Critical - as this calls Critical functions GetSubStreams and LoadSubStreams.
        /// Safe - this does not return data obtained from GetSubStreams.  The call to
        ///        LoadSubStreams won't cause de-serialization of random data.  It'd only
        ///        cause deserializaton of data returned by GetSubStreams which returns an
        ///        object from _subStreams array which is Critical and thus is tracked.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private void LoadState(object node)
        {
            UIElement element = node as UIElement;
            if (element == null)
            {
                return;
            }
 
#pragma warning disable 618
            int persistId = element.PersistId;
#pragma warning restore 618
 
            // Due to bug 1282529, PersistId can be null. Only XAML/BAML-loaded elements have it.
            if (persistId != 0)
            {
                if (this.HasSubStreams(persistId))
                {
                    // Get the properties to restore
                    ArrayList properties = this.GetSubStreams(persistId);
                    LoadSubStreams(element, properties);
                }
 
                if (_customJournaledObjects != null && _customJournaledObjects.Contains(persistId))
                {
                    CustomJournalStateInternal state = 
                        (CustomJournalStateInternal)_customJournaledObjects[persistId];
                    Debug.Assert(state != null);
                    IJournalState customJournalingObject = node as IJournalState;
 
                    //
                    // For below two scenarios, JournalData cannot be restored successfully. For now, we just
                    // simply ignore it and don't throw exception.
                    //
                    //  A. After the tree was created from xaml/baml stream,  some elements might be replaced 
                    //     programatically with new elements which could be created from other xaml/baml by Parser.
                    //
                    //  B. If the loose xaml file has been changed since the journal data was created
                    //
                    // 
 
                    if (customJournalingObject != null)
                    {
                        customJournalingObject.RestoreJournalState(state);
                    }
                }
            }
        }
 
        /// <summary>
        /// Walk the logical tree, and perform an operation on all nodes and children of nodes.
        /// This is slightly more complex than it might otherwise be because it makes no assumptions
        /// that the Logical Tree is strongly typed. It is the responsibility of the operation to make sure
        /// that it can handle the node it receives.
        /// </summary>
        /// <param name="node"></param>
        /// <param name="operation"></param>
        private void WalkLogicalTree(object node, NodeOperation operation)
        {
            if (node != null)
            {
                operation(node);
            }
 
            DependencyObject treeNode = node as DependencyObject;
            if (treeNode == null)
            {
                return;
            }
 
            IEnumerator e = LogicalTreeHelper.GetChildren(treeNode).GetEnumerator();
            if (e == null)
            {
                return;
            }
 
            while (e.MoveNext())
            {
                WalkLogicalTree(e.Current, operation);
            }
        }
 
        /// <SecurityNote>
        /// Critical - as this modifies Critical data member _subStreams.
        /// Safe - as this just initializes it to a safe value.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        internal void Save(Object root)
        {
            if (_subStreams == null)
            {
                _subStreams = new HybridDictionary(3);
            }
            else
            {
                _subStreams.Clear();
            }
            WalkLogicalTree(root, new NodeOperation(this.SaveState));            
        }
 
        internal void Load(Object root)
        {
            if (this.HasAnyData)
            {
                WalkLogicalTree(root, new NodeOperation(this.LoadState));               
            }
        }
 
        /// <SecurityNote>
        /// Critical - as this refers to Critical member _subStreams.
        /// Safe - as this doesn't expose the data
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        internal void Clear()
        {
            _subStreams = null;
            _customJournaledObjects = null; 
        }
 
        #region Private and internal fields and properties
        private BinaryFormatter Formatter
        {
            get
            {
                if (_formatter == null)
                {
                    _formatter = new BinaryFormatter();
                }
 
                return _formatter;
            }
        }
 
        [ThreadStatic]
        static private BinaryFormatter _formatter;
 
        /// <SecurityNote>
        /// Critical - This data is critical for two reasons.  One is that this holds
        ///            data obtained by invoking Serialization under elevation.  The
        ///            other is that we call DeSerialization on this data under elevation.
        /// </SecurityNote>
        [SecurityCritical]
        private HybridDictionary _subStreams = new HybridDictionary(3);
 
        /// <summary>
        /// PersistId->CustomJournalStateInternal map
        /// <see cref="SaveState"/>.
        /// </summary>
        private HybridDictionary _customJournaledObjects;
 
        #endregion Private and internal fields and properties
    }
    #endregion DataStreams class
    
}