//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Diagnostics { using System.Collections.Generic; using System.Diagnostics; using System.Xml; using System.Xml.XPath; using System.Globalization; using System.Text; using System.IO; using System.Runtime; // We have to put something here so that when this item appears in the // debugger, ToString() isn't called. Calling ToString() can cause bad behavior. [DebuggerDisplay("")] class TraceXPathNavigator : XPathNavigator { const int UnlimitedSize = -1; ElementNode root = null; TraceNode current = null; bool closed = false; XPathNodeType state = XPathNodeType.Element; int maxSize; long currentSize; public TraceXPathNavigator(int maxSize) { this.maxSize = maxSize; this.currentSize = 0; } interface IMeasurable { int Size { get; } } class TraceNode { protected TraceNode(XPathNodeType nodeType, ElementNode parent) { this.nodeType = nodeType; this.parent = parent; } internal XPathNodeType NodeType { get { return this.nodeType; } } XPathNodeType nodeType; internal ElementNode parent; } class CommentNode : TraceNode, IMeasurable { internal CommentNode(string text, ElementNode parent) : base(XPathNodeType.Comment, parent) { this.nodeValue = text; } internal string nodeValue; public int Size { get { return this.nodeValue.Length + 8; // } } } class ElementNode : TraceNode, IMeasurable { int attributeIndex = 0; int elementIndex = 0; internal string name; internal string prefix; internal string xmlns; internal List childNodes = new List(); internal List attributes = new List(); internal TextNode text; internal bool movedToText = false; internal ElementNode(string name, string prefix, ElementNode parent, string xmlns) : base(XPathNodeType.Element, parent) { this.name = name; this.prefix = prefix; this.xmlns = xmlns; } internal void Add(TraceNode node) { this.childNodes.Add(node); } //This method returns all subnodes with the given path of local names. Namespaces are ignored. //For all path elements but the last one, the first match is taken. For the last path element, all matches are returned. internal IEnumerable FindSubnodes(string[] headersPath) { #pragma warning disable 618 Fx.Assert(null != headersPath, "Headers path should not be null"); Fx.Assert(headersPath.Length > 0, "There should be more than one item in the headersPath array."); #pragma warning restore 618 if (null == headersPath) { throw new ArgumentNullException("headersPath"); } ElementNode node = this; if (String.CompareOrdinal(node.name, headersPath[0]) != 0) { node = null; } int i = 0; while (null != node && ++i < headersPath.Length) { #pragma warning disable 618 Fx.Assert(null != headersPath[i], "None of the elements in headersPath should be null."); #pragma warning restore 618 ElementNode subNode = null; if (null != node.childNodes) { foreach (TraceNode child in node.childNodes) { if (child.NodeType == XPathNodeType.Element) { ElementNode childNode = child as ElementNode; if (null != childNode && 0 == String.CompareOrdinal(childNode.name, headersPath[i])) { if (headersPath.Length == i + 1) { yield return childNode; } else { subNode = childNode; break; } } } } } node = subNode; } } internal TraceNode MoveToNext() { TraceNode retval = null; if ((this.elementIndex + 1) < this.childNodes.Count) { ++this.elementIndex; retval = this.childNodes[this.elementIndex]; } return retval; } internal bool MoveToFirstAttribute() { this.attributeIndex = 0; return null != this.attributes && this.attributes.Count > 0; } internal bool MoveToNextAttribute() { bool retval = false; if ((this.attributeIndex + 1) < this.attributes.Count) { ++this.attributeIndex; retval = true; } return retval; } internal void Reset() { this.attributeIndex = 0; this.elementIndex = 0; this.movedToText = false; if (null != this.childNodes) { foreach (TraceNode node in this.childNodes) { if (node.NodeType == XPathNodeType.Element) { ElementNode child = node as ElementNode; if (child != null) { child.Reset(); } } } } } internal AttributeNode CurrentAttribute { get { return this.attributes[this.attributeIndex]; } } public int Size { get { int size = 2 * this.name.Length + 6; //upper bound if (!string.IsNullOrEmpty(this.prefix)) { size += this.prefix.Length + 1; } if (!string.IsNullOrEmpty(this.xmlns)) { size += this.xmlns.Length + 9; // xmlns="xmlns" } return size; } } } class AttributeNode : IMeasurable { internal AttributeNode(string name, string prefix, string value, string xmlns) { this.name = name; this.prefix = prefix; this.nodeValue = value; this.xmlns = xmlns; } internal string name; internal string nodeValue; internal string prefix; internal string xmlns; public int Size { get { int size = this.name.Length + this.nodeValue.Length + 5; if (!string.IsNullOrEmpty(this.prefix)) { size += this.prefix.Length + 1; } if (!string.IsNullOrEmpty(this.xmlns)) { size += this.xmlns.Length + 9; //upper bound } return size; } } } class ProcessingInstructionNode : TraceNode, IMeasurable { internal ProcessingInstructionNode(string name, string text, ElementNode parent) : base(XPathNodeType.ProcessingInstruction, parent) { this.name = name; this.text = text; } internal string name; internal string text; public int Size { get { return this.name.Length + this.text.Length + 12; // } } } class TextNode : IMeasurable { internal TextNode(string value) { this.nodeValue = value; } internal string nodeValue; public int Size { get { return this.nodeValue.Length; } } } internal void AddElement(string prefix, string name, string xmlns) { if (this.closed) { #pragma warning disable 618 Fx.Assert("Cannot add data to a closed document"); #pragma warning restore 618 throw new InvalidOperationException(); } else { ElementNode node = new ElementNode(name, prefix, this.CurrentElement, xmlns); if (this.current == null) { this.VerifySize(node); this.root = node; this.current = this.root; } else if (!this.closed) { this.VerifySize(node); this.CurrentElement.Add(node); this.current = node; } } } internal void AddProcessingInstruction(string name, string text) { if (this.current == null) { return; } else { ProcessingInstructionNode node = new ProcessingInstructionNode(name, text, this.CurrentElement); this.VerifySize(node); this.CurrentElement.Add(node); } } internal void AddText(string value) { if (this.closed) { #pragma warning disable 618 Fx.Assert("Cannot add data to a closed document"); #pragma warning restore 618 throw new InvalidOperationException(); } if (this.current == null) { return; } else { if (this.CurrentElement.text == null) { TextNode node = new TextNode(value); this.VerifySize(node); this.CurrentElement.text = node; } else if (!string.IsNullOrEmpty(value)) { this.VerifySize(value); this.CurrentElement.text.nodeValue += value; } } } internal void AddAttribute(string name, string value, string xmlns, string prefix) { if (this.closed) { #pragma warning disable 618 Fx.Assert("Cannot add data to a closed document"); #pragma warning restore 618 throw new InvalidOperationException(); } if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 throw new InvalidOperationException(); } AttributeNode node = new AttributeNode(name, prefix, value, xmlns); this.VerifySize(node); this.CurrentElement.attributes.Add(node); } internal void AddComment(string text) { if (this.closed) { #pragma warning disable 618 Fx.Assert("Cannot add data to a closed document"); #pragma warning restore 618 throw new InvalidOperationException(); } if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 throw new InvalidOperationException(); } CommentNode node = new CommentNode(text, this.CurrentElement); this.VerifySize(node); this.CurrentElement.Add(node); } internal void CloseElement() { if (this.closed) { #pragma warning disable 618 Fx.Assert("The document is already closed."); #pragma warning restore 618 throw new InvalidOperationException(); } else { this.current = this.CurrentElement.parent; if (this.current == null) { this.closed = true; } } } public override string BaseURI { get { return String.Empty; } } public override XPathNavigator Clone() { return this; } public override bool IsEmptyElement { get { bool retval = true; if (this.current != null) { retval = this.CurrentElement.text != null || this.CurrentElement.childNodes.Count > 0; } return retval; } } public override bool IsSamePosition(XPathNavigator other) { return false; } [DebuggerDisplay("")] public override string LocalName { get { return this.Name; } } public override string LookupPrefix(string ns) { return this.LookupPrefix(ns, this.CurrentElement); } string LookupPrefix(string ns, ElementNode node) { string retval = null; if (string.Compare(ns, node.xmlns, StringComparison.Ordinal) == 0) { retval = node.prefix; } else { foreach (AttributeNode attributeNode in node.attributes) { if (string.Compare("xmlns", attributeNode.prefix, StringComparison.Ordinal) == 0) { if (string.Compare(ns, attributeNode.nodeValue, StringComparison.Ordinal) == 0) { retval = attributeNode.name; break; } } } } if (string.IsNullOrEmpty(retval) && node.parent != null) { retval = LookupPrefix(ns, node.parent); } return retval; } public override bool MoveTo(XPathNavigator other) { return false; } public override bool MoveToFirstAttribute() { if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 throw new InvalidOperationException(); } bool retval = this.CurrentElement.MoveToFirstAttribute(); if (retval) { this.state = XPathNodeType.Attribute; } return retval; } public override bool MoveToFirstChild() { if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 throw new InvalidOperationException(); } bool retval = false; if (null != this.CurrentElement.childNodes && this.CurrentElement.childNodes.Count > 0) { this.current = this.CurrentElement.childNodes[0]; this.state = this.current.NodeType; retval = true; } else if ((null == this.CurrentElement.childNodes || this.CurrentElement.childNodes.Count == 0) && this.CurrentElement.text != null) { this.state = XPathNodeType.Text; this.CurrentElement.movedToText = true; retval = true; } return retval; } public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) { return false; } public override bool MoveToId(string id) { return false; } public override bool MoveToNext() { if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 throw new InvalidOperationException(); } bool retval = false; if (this.state != XPathNodeType.Text) { ElementNode parent = this.current.parent; if (parent != null) { TraceNode temp = parent.MoveToNext(); if (temp == null && parent.text != null && !parent.movedToText) { this.state = XPathNodeType.Text; parent.movedToText = true; this.current = parent; retval = true; } else if (temp != null) { this.state = temp.NodeType; retval = true; this.current = temp; } } } return retval; } public override bool MoveToNextAttribute() { if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 throw new InvalidOperationException(); } bool retval = this.CurrentElement.MoveToNextAttribute(); if (retval) { this.state = XPathNodeType.Attribute; } return retval; } public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) { return false; } public override bool MoveToParent() { if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 throw new InvalidOperationException(); } bool retval = false; switch (this.state) { case XPathNodeType.Comment: case XPathNodeType.Element: case XPathNodeType.ProcessingInstruction: if (this.current.parent != null) { this.current = this.current.parent; this.state = this.current.NodeType; retval = true; } break; case XPathNodeType.Attribute: this.state = XPathNodeType.Element; retval = true; break; case XPathNodeType.Text: this.state = XPathNodeType.Element; retval = true; break; case XPathNodeType.Namespace: this.state = XPathNodeType.Element; retval = true; break; } return retval; } public override bool MoveToPrevious() { return false; } public override void MoveToRoot() { this.current = this.root; this.state = XPathNodeType.Element; this.root.Reset(); } [DebuggerDisplay("")] public override string Name { get { string retval = String.Empty; if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 } else { switch (this.state) { case XPathNodeType.Attribute: retval = this.CurrentElement.CurrentAttribute.name; break; case XPathNodeType.Element: retval = this.CurrentElement.name; break; case XPathNodeType.ProcessingInstruction: retval = this.CurrentProcessingInstruction.name; break; } } return retval; } } public override System.Xml.XmlNameTable NameTable { get { return null; } } [DebuggerDisplay("")] public override string NamespaceURI { get { string retval = String.Empty; if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 } else { switch (this.state) { case XPathNodeType.Element: retval = this.CurrentElement.xmlns; break; case XPathNodeType.Attribute: retval = this.CurrentElement.CurrentAttribute.xmlns; break; case XPathNodeType.Namespace: retval = null; break; } } return retval; } } [DebuggerDisplay("")] public override XPathNodeType NodeType { get { return this.state; } } [DebuggerDisplay("")] public override string Prefix { get { string retval = String.Empty; if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 } else { switch (this.state) { case XPathNodeType.Element: retval = this.CurrentElement.prefix; break; case XPathNodeType.Attribute: retval = this.CurrentElement.CurrentAttribute.prefix; break; case XPathNodeType.Namespace: retval = null; break; } } return retval; } } CommentNode CurrentComment { get { return this.current as CommentNode; } } ElementNode CurrentElement { get { return this.current as ElementNode; } } ProcessingInstructionNode CurrentProcessingInstruction { get { return this.current as ProcessingInstructionNode; } } [DebuggerDisplay("")] public override string Value { get { string retval = String.Empty; if (this.current == null) { #pragma warning disable 618 Fx.Assert("Operation is invalid on an empty document"); #pragma warning restore 618 } else { switch (this.state) { case XPathNodeType.Text: retval = this.CurrentElement.text.nodeValue; break; case XPathNodeType.Attribute: retval = this.CurrentElement.CurrentAttribute.nodeValue; break; case XPathNodeType.Comment: retval = this.CurrentComment.nodeValue; break; case XPathNodeType.ProcessingInstruction: retval = this.CurrentProcessingInstruction.text; break; } } return retval; } } internal WriteState WriteState { get { WriteState retval = WriteState.Error; if (this.current == null) { retval = WriteState.Start; } else if (this.closed) { retval = WriteState.Closed; } else { switch (this.state) { case XPathNodeType.Attribute: retval = WriteState.Attribute; break; case XPathNodeType.Element: retval = WriteState.Element; break; case XPathNodeType.Text: retval = WriteState.Content; break; case XPathNodeType.Comment: retval = WriteState.Content; break; } } return retval; } } public override string ToString() { this.MoveToRoot(); StringBuilder sb = new StringBuilder(); EncodingFallbackAwareXmlTextWriter writer = new EncodingFallbackAwareXmlTextWriter(new StringWriter(sb, CultureInfo.CurrentCulture)); writer.WriteNode(this, false); return sb.ToString(); } void VerifySize(IMeasurable node) { this.VerifySize(node.Size); } void VerifySize(string node) { this.VerifySize(node.Length); } void VerifySize(int nodeSize) { if (this.maxSize != TraceXPathNavigator.UnlimitedSize) { if (this.currentSize + nodeSize > this.maxSize) { throw new PlainXmlWriter.MaxSizeExceededException(); } } this.currentSize += nodeSize; } public void RemovePii(string[][] paths) { #pragma warning disable 618 Fx.Assert(null != paths, ""); #pragma warning restore 618 if (paths == null) { throw new ArgumentNullException("paths"); } foreach (string[] path in paths) { RemovePii(path); } } public void RemovePii(string[] path) { RemovePii(path, DiagnosticStrings.PiiList); } public void RemovePii(string[] headersPath, string[] piiList) { #pragma warning disable 618 Fx.Assert(null != this.root, ""); if (this.root == null) { throw new InvalidOperationException(); } foreach (ElementNode node in this.root.FindSubnodes(headersPath)) { Fx.Assert(null != node, ""); MaskSubnodes(node, piiList); } } #pragma warning restore 618 static void MaskElement(ElementNode element) { if (null != element) { element.childNodes.Clear(); element.Add(new CommentNode("Removed", element)); element.text = null; element.attributes = null; } } static void MaskSubnodes(ElementNode element, string[] elementNames) { MaskSubnodes(element, elementNames, false); } static void MaskSubnodes(ElementNode element, string[] elementNames, bool processNodeItself) { #pragma warning disable 618 Fx.Assert(null != elementNames, ""); #pragma warning restore 618 if (elementNames == null) { throw new ArgumentNullException("elementNames"); } if (null != element) { bool recurse = true; if (processNodeItself) { foreach (string elementName in elementNames) { #pragma warning disable 618 Fx.Assert(!String.IsNullOrEmpty(elementName), ""); #pragma warning restore 618 if (0 == String.CompareOrdinal(elementName, element.name)) { MaskElement(element); recurse = false; break; } } } if (recurse) { if (null != element.childNodes) { foreach (ElementNode subNode in element.childNodes) { #pragma warning disable 618 Fx.Assert(null != subNode, ""); #pragma warning restore 618 MaskSubnodes(subNode, elementNames, true); } } } } } } }