using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
namespace Wayne.Lib.Log
{
///
/// Class that is used when logging XML data and wants to utilize the Debuglog mask facilitities.
///
public class XmlLogObject : ILogObject
{
#region Private Classes and Enums
private enum ProcessingInstructionAttributeElementKind
{
AttributeName,
EqualSign,
Value
}
#endregion
#region Static Fields
internal static string XmlMaskNode { get { return "MaskNode"; } }
internal static string XmlMaskAttribute { get { return "MaskAttr"; } }
internal static string XmlNodeName { get { return "node"; } }
internal static string XmlAttributeName { get { return "attr"; } }
internal static string XmlMaskKind { get { return "kind"; } }
internal static string XmlMaskNextNodeName { get { return "."; } }
#endregion
#region Fields
private string xml;
private readonly IXmlElementMask[] elementMasks;
#endregion
#region Construction
///
/// Constructor.
///
/// The XML document.
public XmlLogObject(string xml)
{
this.xml = xml;
}
///
/// Constructor.
///
/// The XML document.
/// Element masking.
public XmlLogObject(string xml, params IXmlElementMask[] elementMasks)
{
this.xml = xml;
this.elementMasks = new IXmlElementMask[elementMasks.Length];
Array.Copy(elementMasks, this.elementMasks, elementMasks.Length);
}
#endregion
#region Properties
///
/// The XML document.
///
public string Xml
{
get { return xml; }
}
bool ILogObject.UsesFormat
{
get { return false; }
}
string ILogObject.Format
{
get { return string.Empty; }
set { }
}
IFormatProvider ILogObject.Provider
{
get { return null; }
set { }
}
#endregion
#region Methods
///
/// Appends this object's logObjects to a StringBuilder-output.
///
/// The StringBuilder.
/// The LogWriter to write the logObject.
/// The indent to be used if many lines.
/// Is this the first line to log?
/// A string holding a generated indent-text (=a number of spaces). Use AppendIndent() to append the indent.
void ILogObject.AppendToStringBuilder(LogWriter logWriter, StringBuilder output,
int indentLength, ref bool isFirstLine, ref string indent)
{
if (DebugLoggerMask.MaskSensitiveData)
{
StringBuilder newXml = new StringBuilder(xml);
// mask Track 2
const string trackTag = "57";
int trackStart = xml.IndexOf(trackTag);
if (trackStart > 0)
{
trackStart += trackTag.Length + 2;
int trackEnd = xml.IndexOf("", trackStart);
string orgTrack = xml.Substring(trackStart, trackEnd - trackStart);
string maskedTrack = DebugLoggerMask.MaskData(orgTrack, DebugLogMaskKind.MaskTrack64);
newXml.Replace(orgTrack, maskedTrack);
}
// mask PAN
const string panTag = "5A";
int panStart = xml.IndexOf(panTag);
if (panStart > 0)
{
panStart += panTag.Length + 2;
int panEnd = xml.IndexOf("", panStart);
string orgPan = xml.Substring(panStart, panEnd - panStart);
string maskedPan = DebugLoggerMask.MaskData(orgPan, DebugLogMaskKind.MaskPan64);
newXml.Replace(orgPan, maskedPan);
}
StringReader xmlStringReader = new StringReader(newXml.ToString());
XmlReader xmlReader = XmlReader.Create(xmlStringReader);
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.OmitXmlDeclaration = true;
xmlWriterSettings.Indent = true;
StringBuilder cleanXml = new StringBuilder();
#if DEBUG
cleanXml.Append("## MASKED ## ");
#endif
XmlWriter xmlWriter = XmlWriter.Create(cleanXml, xmlWriterSettings);
Dictionary maskedNodes = new Dictionary(StringComparer.CurrentCultureIgnoreCase);
Dictionary> maskedAttributes = new Dictionary>(StringComparer.CurrentCultureIgnoreCase);
int emptyNodeDepth = int.MaxValue;
int maskNodeDepth = int.MaxValue;
int maskDigitsNodeDepth = int.MaxValue;
int maskDigitsAppendHashNodeDepth = int.MaxValue;
int maskHexNodeDepth = int.MaxValue;
int maskHexAppendHashNodeDepth = int.MaxValue;
int maskPanNodeDepth = int.MaxValue;
int maskPanAppendHashNodeDepth = int.MaxValue;
int maskEmbeddedPanNodeDepth = int.MaxValue;
int maskEmbeddedPanAppendHashNodeDepth = int.MaxValue;
DebugLogMaskKind maskNextNode = DebugLogMaskKind.None;
bool skipWhitespace = true;
if (elementMasks != null)
{
foreach (IXmlElementMask elementMask in elementMasks)
{
XmlAttributeMask xmlAttributeMask = elementMask as XmlAttributeMask;
if (xmlAttributeMask != null)
AddXmlAttributeMask(maskedAttributes, xmlAttributeMask.NodeName, xmlAttributeMask.AttributeName, xmlAttributeMask.MaskKind);
else
{
XmlNodeMask xmlNodeMask = elementMask as XmlNodeMask;
if (xmlNodeMask != null)
AddXmlNodeMask(maskedNodes, xmlNodeMask.NodeName, xmlNodeMask.MaskKind);
}
}
}
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.ProcessingInstruction)
{
if (xmlReader.Name.Equals(XmlMaskNode))
{
Dictionary attributes = GetProcessingInstructionAttributes(xmlReader);
string nodeName;
if (attributes.TryGetValue(XmlNodeName, out nodeName))
{
DebugLogMaskKind maskKind = InterpretMaskKind(attributes);
if (nodeName.Equals(XmlMaskNextNodeName))
maskNextNode = maskKind;
else
AddXmlNodeMask(maskedNodes, nodeName, maskKind);
}
}
else if (xmlReader.Name.Equals(XmlMaskAttribute))
{
Dictionary attributes = GetProcessingInstructionAttributes(xmlReader);
string nodeName;
if (attributes.TryGetValue(XmlNodeName, out nodeName))
{
string attributeName;
if (attributes.TryGetValue(XmlAttributeName, out attributeName))
{
DebugLogMaskKind maskKind = InterpretMaskKind(attributes);
AddXmlAttributeMask(maskedAttributes, nodeName, attributeName, maskKind);
}
}
}
}
CopyNodeContent(xmlReader, xmlWriter, maskedNodes, maskedAttributes,
ref emptyNodeDepth, ref maskNodeDepth, ref maskDigitsNodeDepth, ref maskDigitsAppendHashNodeDepth,
ref maskHexNodeDepth, ref maskHexAppendHashNodeDepth,
ref maskPanNodeDepth, ref maskPanAppendHashNodeDepth,
ref maskEmbeddedPanNodeDepth, ref maskEmbeddedPanAppendHashNodeDepth,
ref maskNextNode, ref skipWhitespace);
}
xmlWriter.Close();
xmlReader.Close();
StringLogObject.AppendObjectToStringBuilder(cleanXml, output, logWriter, indentLength, ref isFirstLine, ref indent);
}
else
{
try
{
StringBuilder sb = new StringBuilder();
int endOfString = xml.IndexOf('\0');
if (endOfString > -1)
xml = xml.Substring(0, endOfString); // Sometimes there come strings with null-characters which the XML reader doesn't like...
XmlReader xmlReader = XmlReader.Create(new StringReader(xml));
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter xmlWriter = XmlWriter.Create(sb, settings);
xmlWriter.WriteNode(xmlReader, false);
xmlWriter.Flush();
StringLogObject.AppendObjectToStringBuilder(sb.ToString(), output, logWriter, indentLength, ref isFirstLine, ref indent);
}
catch (ArgumentException)
{
// Just log the xml as a string.
StringLogObject.AppendObjectToStringBuilder(xml, output, logWriter, indentLength, ref isFirstLine, ref indent);
}
}
}
private static void AddXmlNodeMask(IDictionary maskedNodes, string nodeName, DebugLogMaskKind maskKind)
{
XmlNodeMask existingXmlNodeMask;
if (maskedNodes.TryGetValue(nodeName, out existingXmlNodeMask))
{
if (maskKind == DebugLogMaskKind.None)
maskedNodes.Remove(nodeName);
else
existingXmlNodeMask.MaskKind = maskKind;
}
else if (maskKind != DebugLogMaskKind.None)
maskedNodes.Add(nodeName, new XmlNodeMask(nodeName, maskKind));
}
private static void AddXmlAttributeMask(IDictionary> maskedAttributes, string nodeName, string attributeName, DebugLogMaskKind maskKind)
{
bool newDict = false;
Dictionary attributeMaskDict;
if (!maskedAttributes.TryGetValue(nodeName, out attributeMaskDict))
{
attributeMaskDict = new Dictionary(StringComparer.CurrentCultureIgnoreCase);
newDict = true;
}
XmlAttributeMask existingXmlAttributeMask;
if (attributeMaskDict.TryGetValue(attributeName, out existingXmlAttributeMask))
{
if (maskKind == DebugLogMaskKind.None)
attributeMaskDict.Remove(attributeName);
else
existingXmlAttributeMask.MaskKind = maskKind;
}
else if (maskKind != DebugLogMaskKind.None)
attributeMaskDict.Add(attributeName, new XmlAttributeMask(nodeName, attributeName, maskKind));
if (newDict)
maskedAttributes.Add(nodeName, attributeMaskDict);
}
#endregion
#region GetProcessingInstructionAttributes
private static Dictionary GetProcessingInstructionAttributes(XmlReader xmlReader)
{
Dictionary attributes = new Dictionary();
string[] elements = xmlReader.Value.Split(' ');
ProcessingInstructionAttributeElementKind elementKind = ProcessingInstructionAttributeElementKind.AttributeName;
string currentAttributeName = string.Empty;
int equalSignPos;
foreach (string element in elements)
{
if (!string.IsNullOrEmpty(element))
{
switch (elementKind)
{
case ProcessingInstructionAttributeElementKind.AttributeName:
equalSignPos = element.IndexOf('=');
if (equalSignPos > -1)
{
if (equalSignPos > 0)
{
currentAttributeName = element.Substring(0, equalSignPos);
if (equalSignPos == element.Length - 1)
elementKind = ProcessingInstructionAttributeElementKind.Value;
else
{
string value = element.Substring(equalSignPos + 1);
if (value.StartsWith("\"") && value.EndsWith("\""))
attributes[currentAttributeName] = value.Substring(1, value.Length - 2);
elementKind = ProcessingInstructionAttributeElementKind.AttributeName;
}
}
}
else
{
currentAttributeName = element;
elementKind = ProcessingInstructionAttributeElementKind.EqualSign;
}
break;
case ProcessingInstructionAttributeElementKind.EqualSign:
if (element.Equals("="))
elementKind = ProcessingInstructionAttributeElementKind.Value;
else if (element.StartsWith("="))
{
string value = element.Substring(1);
if (value.StartsWith("\"") && value.EndsWith("\""))
attributes[currentAttributeName] = value.Substring(1, value.Length - 2);
elementKind = ProcessingInstructionAttributeElementKind.AttributeName;
}
break;
case ProcessingInstructionAttributeElementKind.Value:
if (element.StartsWith("\"") && element.EndsWith("\""))
attributes[currentAttributeName] = element.Substring(1, element.Length - 2);
elementKind = ProcessingInstructionAttributeElementKind.AttributeName;
break;
}
}
}
return attributes;
}
#endregion
#region InterpretMaskKind
private static DebugLogMaskKind InterpretMaskKind(Dictionary attributes)
{
string maskKindValue;
if (attributes.TryGetValue(XmlMaskKind, out maskKindValue))
{
try
{
return (DebugLogMaskKind)Enum.Parse(typeof(DebugLogMaskKind), maskKindValue, true);
}
catch (ArgumentException) { }
}
return DebugLogMaskKind.Remove;
}
#endregion
#region CopyNodeContent
private static void CopyNodeContent(XmlReader xmlReader, XmlWriter xmlWriter,
Dictionary maskedNodes, Dictionary> maskedAttributes,
ref int emptyNodeDepth, ref int maskNodeDepth, ref int maskDigitsNodeDepth, ref int maskDigitsAppendHashNodeDepth,
ref int maskHexNodeDepth, ref int maskHexAppendHashNodeDepth, ref int maskPanNodeDepth, ref int maskPanAppendHashNodeDepth,
ref int maskEmbeddedPanNodeDepth, ref int maskEmbeddedPanAppendHashNodeDepth, ref DebugLogMaskKind maskNextNode, ref bool skipWhitespace)
{
switch (xmlReader.NodeType)
{
case XmlNodeType.Element:
if (xmlReader.Depth < emptyNodeDepth)
{
bool skip = false;
XmlNodeMask xmlNodeMask;
DebugLogMaskKind maskKind;
if (maskNextNode != DebugLogMaskKind.None)
{
maskKind = maskNextNode;
maskNextNode = DebugLogMaskKind.None;
}
else if (maskedNodes.TryGetValue(xmlReader.LocalName, out xmlNodeMask))
maskKind = xmlNodeMask.MaskKind;
else
maskKind = DebugLogMaskKind.None;
switch (maskKind)
{
case DebugLogMaskKind.Remove:
xmlReader.Skip();
skip = true;
break;
case DebugLogMaskKind.Empty:
emptyNodeDepth = xmlReader.Depth;
break;
case DebugLogMaskKind.Mask:
maskNodeDepth = xmlReader.Depth;
break;
case DebugLogMaskKind.MaskDigits:
maskDigitsNodeDepth = xmlReader.Depth;
break;
case DebugLogMaskKind.MaskDigitsAppendHash:
maskDigitsAppendHashNodeDepth = xmlReader.Depth;
break;
case DebugLogMaskKind.MaskHex:
maskHexNodeDepth = xmlReader.Depth;
break;
case DebugLogMaskKind.MaskHexAppendHash:
maskHexAppendHashNodeDepth = xmlReader.Depth;
break;
case DebugLogMaskKind.MaskPan:
maskPanNodeDepth = xmlReader.Depth;
break;
case DebugLogMaskKind.MaskPanAppendHash:
maskPanAppendHashNodeDepth = xmlReader.Depth;
break;
case DebugLogMaskKind.MaskEmbeddedPan:
maskEmbeddedPanNodeDepth = xmlReader.Depth;
break;
case DebugLogMaskKind.MaskEmbeddedPanAppendHash:
maskEmbeddedPanAppendHashNodeDepth = xmlReader.Depth;
break;
}
if (!skip)
{
skipWhitespace = false;
xmlWriter.WriteStartElement(xmlReader.Prefix, xmlReader.LocalName, xmlReader.NamespaceURI);
if ((xmlReader.Depth < emptyNodeDepth) && xmlReader.HasAttributes)
{
bool manualAttributes = false;
Dictionary attributeMaskDict;
if (maskedAttributes.TryGetValue(xmlReader.LocalName, out attributeMaskDict) && (attributeMaskDict.Count > 0))
{
manualAttributes = true;
while (xmlReader.MoveToNextAttribute())
{
if (!xmlReader.Prefix.Equals("xmlns")) // This attribute is already written.
{
bool writeAttribute = true;
string value = xmlReader.Value;
XmlAttributeMask xmlAttributeMask;
if (attributeMaskDict.TryGetValue(xmlReader.Name, out xmlAttributeMask))
{
switch (xmlAttributeMask.MaskKind)
{
case DebugLogMaskKind.Remove: writeAttribute = false; break;
case DebugLogMaskKind.Empty: value = string.Empty; break;
default: value = DebugLoggerMask.MaskData(value, xmlAttributeMask.MaskKind); break;
}
}
if (writeAttribute)
{
xmlWriter.WriteStartAttribute(xmlReader.Prefix, xmlReader.LocalName, xmlReader.NamespaceURI);
xmlWriter.WriteValue(value);
xmlWriter.WriteEndAttribute();
}
}
}
xmlReader.MoveToElement(); // Move the reader back to the element node.
}
if (!manualAttributes)
xmlWriter.WriteAttributes(xmlReader, true);
}
if (xmlReader.IsEmptyElement)
{
if ((emptyNodeDepth < int.MaxValue) && (xmlReader.Depth <= emptyNodeDepth))
emptyNodeDepth = int.MaxValue;
if ((maskNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskNodeDepth))
maskNodeDepth = int.MaxValue;
if ((maskDigitsNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskDigitsNodeDepth))
maskDigitsNodeDepth = int.MaxValue;
if ((maskDigitsAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskDigitsAppendHashNodeDepth))
maskDigitsAppendHashNodeDepth = int.MaxValue;
if ((maskHexNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskHexNodeDepth))
maskHexNodeDepth = int.MaxValue;
if ((maskHexAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskHexAppendHashNodeDepth))
maskHexAppendHashNodeDepth = int.MaxValue;
if ((maskPanNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskPanNodeDepth))
maskPanNodeDepth = int.MaxValue;
if ((maskPanAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskPanAppendHashNodeDepth))
maskPanAppendHashNodeDepth = int.MaxValue;
if ((maskEmbeddedPanNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskEmbeddedPanNodeDepth))
maskEmbeddedPanNodeDepth = int.MaxValue;
if ((maskEmbeddedPanAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskEmbeddedPanAppendHashNodeDepth))
maskEmbeddedPanAppendHashNodeDepth = int.MaxValue;
xmlWriter.WriteEndElement();
}
}
}
break;
case XmlNodeType.Text:
if (xmlReader.Depth < emptyNodeDepth)
{
if (xmlReader.Depth >= maskNodeDepth)
xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.Mask));
else if (xmlReader.Depth >= maskDigitsNodeDepth)
xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskDigits));
else if (xmlReader.Depth >= maskDigitsAppendHashNodeDepth)
xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskDigitsAppendHash));
else if (xmlReader.Depth >= maskHexNodeDepth)
xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskHex));
else if (xmlReader.Depth >= maskHexAppendHashNodeDepth)
xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskHexAppendHash));
else if (xmlReader.Depth >= maskPanNodeDepth)
xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskPan));
else if (xmlReader.Depth >= maskPanAppendHashNodeDepth)
xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskPanAppendHash));
else if (xmlReader.Depth >= maskEmbeddedPanNodeDepth)
xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskEmbeddedPan));
else if (xmlReader.Depth >= maskEmbeddedPanAppendHashNodeDepth)
xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskEmbeddedPanAppendHash));
else
xmlWriter.WriteString(xmlReader.Value);
skipWhitespace = false;
}
break;
case XmlNodeType.Whitespace:
case XmlNodeType.SignificantWhitespace:
if (!skipWhitespace && (xmlReader.Depth < emptyNodeDepth))
xmlWriter.WriteWhitespace(xmlReader.Value);
break;
case XmlNodeType.CDATA:
if (xmlReader.Depth < emptyNodeDepth)
{
xmlWriter.WriteCData(xmlReader.Value);
skipWhitespace = false;
}
break;
case XmlNodeType.EntityReference:
if (xmlReader.Depth < emptyNodeDepth)
{
xmlWriter.WriteEntityRef(xmlReader.Name);
skipWhitespace = false;
}
break;
case XmlNodeType.XmlDeclaration:
//if (xmlReader.Depth < emptyNodeDepth)
// xmlWriter.WriteProcessingInstruction(xmlReader.Name, xmlReader.Value);
skipWhitespace = true;
break;
case XmlNodeType.ProcessingInstruction:
//if (xmlReader.Depth < emptyNodeDepth)
// xmlWriter.WriteProcessingInstruction(xmlReader.Name, xmlReader.Value);
skipWhitespace = true;
break;
case XmlNodeType.DocumentType:
if (xmlReader.Depth < emptyNodeDepth)
{
xmlWriter.WriteDocType(xmlReader.Name, xmlReader.GetAttribute("PUBLIC"), xmlReader.GetAttribute("SYSTEM"), xmlReader.Value);
skipWhitespace = false;
}
break;
case XmlNodeType.Comment:
skipWhitespace = false;
if (xmlReader.Depth < emptyNodeDepth)
{
xmlWriter.WriteComment(xmlReader.Value);
skipWhitespace = false;
}
break;
case XmlNodeType.EndElement:
if ((emptyNodeDepth < int.MaxValue) && (xmlReader.Depth <= emptyNodeDepth))
emptyNodeDepth = int.MaxValue;
if ((maskNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskNodeDepth))
maskNodeDepth = int.MaxValue;
if ((maskDigitsNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskDigitsNodeDepth))
maskDigitsNodeDepth = int.MaxValue;
if ((maskDigitsAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskDigitsAppendHashNodeDepth))
maskDigitsAppendHashNodeDepth = int.MaxValue;
if ((maskHexNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskHexNodeDepth))
maskHexNodeDepth = int.MaxValue;
if ((maskHexAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskHexAppendHashNodeDepth))
maskHexAppendHashNodeDepth = int.MaxValue;
if ((maskPanNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskPanNodeDepth))
maskPanNodeDepth = int.MaxValue;
if ((maskPanAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskPanAppendHashNodeDepth))
maskPanAppendHashNodeDepth = int.MaxValue;
if ((maskEmbeddedPanNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskEmbeddedPanNodeDepth))
maskEmbeddedPanNodeDepth = int.MaxValue;
if ((maskEmbeddedPanAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskEmbeddedPanAppendHashNodeDepth))
maskEmbeddedPanAppendHashNodeDepth = int.MaxValue;
if (xmlReader.Depth < emptyNodeDepth)
{
xmlWriter.WriteFullEndElement();
skipWhitespace = false;
}
break;
}
}
#endregion
}
}