using System.Globalization;
using System.Xml;
using System.Text;
namespace Wayne.Lib.Log
{
///
/// Static support class for managing masking of debug logs.
///
public static class DebugLoggerMask
{
#region Fields
private static bool forceMask;
#endregion
#region XmlWriter Support
///
/// Writes processing instructions to an XML document to mask a node.
/// The masking will be active from this point forward in the XML document (until the end or the instruction is redefined).
///
/// The XML writer.
/// The name of the XML node.
/// The type of masking to use.
public static void MaskXmlNode(XmlWriter xmlWriter, string nodeName, DebugLogMaskKind maskKind)
{
xmlWriter.WriteProcessingInstruction(XmlLogObject.XmlMaskNode,
string.Concat(XmlLogObject.XmlNodeName, "=\"", nodeName, "\" ",
XmlLogObject.XmlMaskKind, "=\"", maskKind, "\""));
}
///
/// Writes processing instructions to an XML document to mask the next node.
///
/// The XML writer.
/// The type of masking to use.
public static void MaskNextXmlNode(XmlWriter xmlWriter, DebugLogMaskKind maskKind)
{
xmlWriter.WriteProcessingInstruction(XmlLogObject.XmlMaskNode,
string.Concat(XmlLogObject.XmlNodeName, "=\"", XmlLogObject.XmlMaskNextNodeName, "\" ",
XmlLogObject.XmlMaskKind, "=\"", maskKind, "\""));
}
///
/// Writes processing instructions to an XML document to mask an attribute.
/// The masking will be active from this point forward in the XML document (until the end or the instruction is redefined).
///
/// The XML writer.
/// The name of the XML node.
/// The name of the node attribute.
/// The type of masking to use.
public static void MaskXmlAttribute(XmlWriter xmlWriter, string nodeName, string attributeName, DebugLogMaskKind maskKind)
{
xmlWriter.WriteProcessingInstruction(XmlLogObject.XmlMaskAttribute,
string.Concat(XmlLogObject.XmlNodeName, "=\"", nodeName, "\" ",
XmlLogObject.XmlAttributeName, "=\"", attributeName, "\" ",
XmlLogObject.XmlMaskKind, "=\"", maskKind, "\""));
}
#endregion
#region XmlDocument Support
///
/// Returns the XmlNode needed to be added to an XML document to mask a node.
/// The masking will be active from the point forward where the node is inserted (until the end or the instruction is redefined).
///
/// The XML document.
/// The name of the XML node.
/// The type of masking to use.
public static XmlProcessingInstruction CreateXmlNodeToMaskNode(XmlDocument xmlDocument, string nodeName, DebugLogMaskKind maskKind)
{
return xmlDocument.CreateProcessingInstruction(XmlLogObject.XmlMaskNode,
string.Concat(XmlLogObject.XmlNodeName, "=\"", nodeName, "\" ",
XmlLogObject.XmlMaskKind, "=\"", maskKind, "\""));
}
///
/// Returns the XmlNode needed to be added to an XML document to mask the next node.
///
/// The XML document.
/// The type of masking to use.
public static XmlProcessingInstruction CreateXmlNodeToMaskNextNode(XmlDocument xmlDocument, DebugLogMaskKind maskKind)
{
return xmlDocument.CreateProcessingInstruction(XmlLogObject.XmlMaskNode,
string.Concat(XmlLogObject.XmlNodeName, "=\"", XmlLogObject.XmlMaskNextNodeName, "\" ",
XmlLogObject.XmlMaskKind, "=\"", maskKind, "\""));
}
///
/// Returns the XmlNode needed to be added to an XML document to mask an attribute.
/// The masking will be active from this point forward in the XML document (until the end or the instruction is redefined).
///
/// The XML document.
/// The name of the XML node.
/// The name of the node attribute.
/// The type of masking to use.
public static XmlProcessingInstruction CreateXmlNodeToMaskAttribute(XmlDocument xmlDocument, string nodeName, string attributeName, DebugLogMaskKind maskKind)
{
return xmlDocument.CreateProcessingInstruction(XmlLogObject.XmlMaskAttribute,
string.Concat(XmlLogObject.XmlNodeName, "=\"", nodeName, "\" ",
XmlLogObject.XmlAttributeName, "=\"", attributeName, "\" ",
XmlLogObject.XmlMaskKind, "=\"", maskKind, "\""));
}
#endregion
#region Properties
///
/// Turn on/off masking in debug mode.
///
public static bool ForceMask
{
get { return forceMask; }
set { forceMask = value; }
}
///
/// Should sensitive data be masked?
///
public static bool MaskSensitiveData
{
get
{
#if DEBUG
return forceMask;
#else
return true;
#endif
}
}
#endregion
#region Methods
///
/// Masks the data the requested way.
///
/// The data to mask.
/// The kind of masking to perform.
///
public static string MaskData(string data, DebugLogMaskKind maskKind)
{
#if DEBUG
if (forceMask)
#endif
{
switch (maskKind)
{
case DebugLogMaskKind.Remove: return string.Empty;
case DebugLogMaskKind.Empty: return string.Empty;
case DebugLogMaskKind.Mask: return "****";
case DebugLogMaskKind.MaskDigits:
{
StringBuilder maskedData = new StringBuilder();
if (!string.IsNullOrEmpty(data))
{
for (int i = 0; i < data.Length; i++)
{
char dataChar = data[i];
if ((dataChar >= '0') && (dataChar <= '9'))
maskedData.Append('*');
else
maskedData.Append(dataChar);
}
}
return maskedData.ToString();
}
case DebugLogMaskKind.MaskDigitsAppendHash:
{
StringBuilder maskedData = new StringBuilder();
StringBuilder digits = new StringBuilder();
if (!string.IsNullOrEmpty(data))
{
for (int i = 0; i < data.Length; i++)
{
char dataChar = data[i];
if ((dataChar >= '0') && (dataChar <= '9'))
{
maskedData.Append('*');
digits.Append(dataChar);
}
else
maskedData.Append(dataChar);
}
}
return string.Concat(maskedData, "[#", GetHashCode(digits.ToString()), "#]");
}
case DebugLogMaskKind.MaskHex:
{
StringBuilder maskedData = new StringBuilder();
if (!string.IsNullOrEmpty(data))
{
for (int i = 0; i < data.Length; i++)
{
char dataChar = data[i];
if ((dataChar >= '0') && (dataChar <= '9') || (dataChar >= 'a') && (dataChar <= 'f') || (dataChar >= 'A') && (dataChar <= 'F'))
maskedData.Append('*');
else
maskedData.Append(dataChar);
}
}
return maskedData.ToString();
}
case DebugLogMaskKind.MaskHexAppendHash:
{
StringBuilder maskedData = new StringBuilder();
StringBuilder hex = new StringBuilder();
if (!string.IsNullOrEmpty(data))
{
for (int i = 0; i < data.Length; i++)
{
char dataChar = data[i];
if ((dataChar >= '0') && (dataChar <= '9') || (dataChar >= 'a') && (dataChar <= 'f') || (dataChar >= 'A') && (dataChar <= 'F'))
{
maskedData.Append('*');
hex.Append(dataChar);
}
else
maskedData.Append(dataChar);
}
}
return string.Concat(maskedData, "[#", GetHashCode(hex.ToString()), "#]");
}
case DebugLogMaskKind.MaskPan:
{
if (!string.IsNullOrEmpty(data) && (data.Length > 8))
return string.Concat(data.Substring(0, 4), string.Empty.PadLeft(data.Length - 8, '*'), data.Substring(data.Length - 4, 4));
return data;
}
case DebugLogMaskKind.MaskPan64:
{
if (!string.IsNullOrEmpty(data) && (data.Length > 10))
return string.Concat(data.Substring(0, 6), string.Empty.PadLeft(data.Length - 10, '*'), data.Substring(data.Length - 4, 4));
return data;
}
case DebugLogMaskKind.MaskTrack64:
{
if (!string.IsNullOrEmpty(data) && (data.Length > 10))
{
int sepIndex = data.IndexOf("=");
if (sepIndex < 0)
{
return string.Concat(data.Substring(0, 6), string.Empty.PadLeft(data.Length - 10, '*'), data.Substring(data.Length - 4, 4));
}
else
{
string panData = data.Substring(0, sepIndex);
string otherData = string.Empty;
if (data.Length > sepIndex + 1)
{
otherData = data.Substring(sepIndex + 1, data.Length - sepIndex - 1);
}
return string.Concat(panData.Substring(0, 6), string.Empty.PadLeft(panData.Length - 10, '*'), panData.Substring(panData.Length - 4, 4), "=", otherData);
}
}
return data;
}
case DebugLogMaskKind.MaskPanAppendHash:
{
if (!string.IsNullOrEmpty(data) && (data.Length > 8))
return string.Concat(data.Substring(0, 4), string.Empty.PadLeft(data.Length - 8, '*'), data.Substring(data.Length - 4, 4), "[#", GetHashCode(data), "#]");
return data;
}
case DebugLogMaskKind.MaskEmbeddedPan:
case DebugLogMaskKind.MaskEmbeddedPanAppendHash:
{
StringBuilder maskedData = new StringBuilder();
int stringBuilderOffset = 0;
if (!string.IsNullOrEmpty(data))
{
int digitStart = -1;
for (int i = 0; i < data.Length; i++)
{
char dataChar = data[i];
bool isDigit = (dataChar >= '0') && (dataChar <= '9');
bool isLastChar = (i == data.Length - 1);
bool inDigitSequence = (digitStart > -1);
maskedData.Append(dataChar);
if (isDigit && (!isLastChar || !inDigitSequence))
{
if (digitStart == -1)
digitStart = i + stringBuilderOffset;
}
else
{
if (digitStart > -1)
{
int digitLength = i + stringBuilderOffset - digitStart;
if (isLastChar)
digitLength++;
if (digitLength > 8)
{
if (maskKind == DebugLogMaskKind.MaskEmbeddedPanAppendHash)
{
string hashedPan = string.Concat("[#", GetHashCode(maskedData.ToString(digitStart, digitLength)), "#]");
maskedData.Remove(digitStart + 4, digitLength - 8).Insert(digitStart + 4, "*", digitLength - 8).Insert(digitStart + digitLength, hashedPan);
stringBuilderOffset += hashedPan.Length;
}
else
maskedData.Remove(digitStart + 4, digitLength - 8).Insert(digitStart + 4, "*", digitLength - 8);
}
}
digitStart = -1;
}
}
}
return maskedData.ToString();
}
}
}
return data;
}
///
/// Masks the data the requested way.
///
/// The binary data to mask.
/// Should the original length of the bytes be shown in the log?
///
public static string MaskData(byte[] bytes, bool showLength)
{
return MaskData(bytes, showLength, false);
}
///
/// Masks the data the requested way.
///
/// The binary data to mask.
/// Should the original length of the bytes be shown in the log?
/// Should a hash value be calculated and shown in the log?
///
public static string MaskData(byte[] bytes, bool showLength, bool showHashValue)
{
bool isDebug = false;
#if DEBUG
isDebug = true;
#endif
if (forceMask || !isDebug)
{
StringBuilder text = new StringBuilder();
if (!showLength)
text.Append("***# bytes***");
else if (bytes == null)
text.Append("***null bytes***");
else
{
text.Append(bytes.Length);
text.Append(" bytes");
}
if (showHashValue)
{
if (showLength)
text.Append(",");
if (bytes != null)
{
int hashValue = 0;
for (int i = 0; i < bytes.Length; i++)
hashValue = (hashValue * 31) ^ bytes[i]; // Just a simple "hash" calculation of a byte array.
text.Append("[#");
text.Append(hashValue.ToString("X8"));
text.Append("#]");
}
else
text.Append("[#--------#]");
}
return string.Concat("***", text, "***");
}
return Strings.ByteArrayToString(bytes, StringsByteArrayFormat.CompactHex);
}
private static string GetHashCode(string text)
{
string fullHashCode = text.GetHashCode().ToString("X8");
int firstHashHalf = int.Parse(fullHashCode.Substring(0, 4), NumberStyles.HexNumber);
int lastHashHalf = int.Parse(fullHashCode.Substring(4, 4), NumberStyles.HexNumber);
int hashedHash = firstHashHalf ^ lastHashHalf;
return hashedHash.ToString("X4");
}
#endregion
}
}