This is an automated email from the ASF dual-hosted git repository.
freeandnil pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4net.git
The following commit(s) were added to refs/heads/master by this push:
new 42a77cb0 #280 harden the handling of invalid characters for the
XmlLayout classes
42a77cb0 is described below
commit 42a77cb05abfd5b1ad7224f0861a932eac95c7c8
Author: Jan Friedrich <[email protected]>
AuthorDate: Tue Feb 17 08:59:08 2026 +0100
#280 harden the handling of invalid characters for the XmlLayout classes
---
.../3.3.0/280-harden-invalid-characters.xml | 10 ++++
.../Layout/XmlLayoutSchemaLog4jTest.cs | 53 +++++++++++++++++++++-
src/log4net.Tests/Layout/XmlLayoutTest.cs | 50 ++++++++++++++++++++
src/log4net.Tests/Util/TransformTest.cs | 2 +-
src/log4net/Layout/Internal/XmlWriterExtensions.cs | 15 +++++-
src/log4net/Layout/XmlLayout.cs | 45 +++++++++---------
src/log4net/Layout/XmlLayoutSchemaLog4j.cs | 38 ++++++++--------
src/log4net/Util/Transform.cs | 5 +-
8 files changed, 165 insertions(+), 53 deletions(-)
diff --git a/src/changelog/3.3.0/280-harden-invalid-characters.xml
b/src/changelog/3.3.0/280-harden-invalid-characters.xml
new file mode 100644
index 00000000..512ab807
--- /dev/null
+++ b/src/changelog/3.3.0/280-harden-invalid-characters.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="https://logging.apache.org/xml/ns"
+ xsi:schemaLocation="https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+ type="fixed">
+ <issue id="280" link="https://github.com/apache/logging-log4net/pull/280"/>
+ <description format="asciidoc">
+ harden the handling of invalid characters for the XmlLayout classes (by
@FreeAndNil in https://github.com/apache/logging-log4net/pull/280[#280])
+ </description>
+</entry>
\ No newline at end of file
diff --git a/src/log4net.Tests/Layout/XmlLayoutSchemaLog4jTest.cs
b/src/log4net.Tests/Layout/XmlLayoutSchemaLog4jTest.cs
index d7d14de0..06587783 100644
--- a/src/log4net.Tests/Layout/XmlLayoutSchemaLog4jTest.cs
+++ b/src/log4net.Tests/Layout/XmlLayoutSchemaLog4jTest.cs
@@ -18,12 +18,11 @@
#endregion
using System;
-
using log4net.Config;
+using log4net.Core;
using log4net.Layout;
using log4net.Repository;
using log4net.Tests.Appender;
-
using NUnit.Framework;
namespace log4net.Tests.Layout
@@ -63,5 +62,55 @@ void ThrowAndLog(int foo)
}
}
}
+
+ /// <summary>
+ /// Tests the serialization of invalid characters in the Properties
dictionary
+ /// </summary>
+ [Test]
+ public void InvalidCharacterTest()
+ {
+ StringAppender stringAppender = new() { Layout = new
XmlLayoutSchemaLog4J() };
+
+ ILoggerRepository repository =
LogManager.CreateRepository(Guid.NewGuid().ToString());
+ BasicConfigurator.Configure(repository, stringAppender);
+ ILog log = LogManager.GetLogger(repository.Name, "TestLogger");
+
+ Log();
+
+ string logEventXml = stringAppender.GetString();
+ Assert.That(logEventXml, Does.Contain("us?er"));
+ Assert.That(logEventXml, Does.Contain("A?B"));
+ Assert.That(logEventXml, Does.Contain("Log?ger"));
+ Assert.That(logEventXml, Does.Contain("Thread?Name"));
+ Assert.That(logEventXml, Does.Contain("Do?main"));
+ Assert.That(logEventXml, Does.Contain("Ident?ity"));
+ Assert.That(logEventXml, Does.Contain("User?Name"));
+ Assert.That(logEventXml, Does.Contain("Mess?age"));
+ Assert.That(logEventXml, Does.Contain("oh?my"));
+
+ void Log()
+ {
+ // Build a LoggingEvent with an XML invalid character in a property
value
+ LoggingEventData data = new()
+ {
+ LoggerName = "Log\u0001ger",
+ Level = Level.Info,
+ TimeStampUtc = DateTime.UtcNow,
+ ThreadName = "Thread\u0001Name",
+ Domain = "Do\u0001main",
+ Identity = "Ident\u0001ity",
+ UserName = "User\u0001Name",
+ Message = "Mess\u0001age",
+ ExceptionString = "oh\u0001my",
+ Properties = new()
+ };
+
+ // Value contains U+0001 which is illegal in XML 1.0
+ data.Properties["us\u0001er"] = "A\u0001B";
+
+ // Log the event
+ log.Logger.Log(new(null, repository, data));
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/log4net.Tests/Layout/XmlLayoutTest.cs
b/src/log4net.Tests/Layout/XmlLayoutTest.cs
index 2f323a3b..3c0dc79d 100644
--- a/src/log4net.Tests/Layout/XmlLayoutTest.cs
+++ b/src/log4net.Tests/Layout/XmlLayoutTest.cs
@@ -371,4 +371,54 @@ void Bar(int foo)
Assert.That(sub, Does.Not.Contain(">"));
}
}
+
+ /// <summary>
+ /// Tests the serialization of invalid characters in the Properties
dictionary
+ /// </summary>
+ [Test]
+ public void InvalidCharacterTest()
+ {
+ StringAppender stringAppender = new() { Layout = new XmlLayout() };
+
+ ILoggerRepository repository =
LogManager.CreateRepository(Guid.NewGuid().ToString());
+ BasicConfigurator.Configure(repository, stringAppender);
+ ILog log = LogManager.GetLogger(repository.Name, "TestLogger");
+
+ Log();
+
+ string logEventXml = stringAppender.GetString();
+ Assert.That(logEventXml, Does.Contain("us?er"));
+ Assert.That(logEventXml, Does.Contain("A?B"));
+ Assert.That(logEventXml, Does.Contain("Log?ger"));
+ Assert.That(logEventXml, Does.Contain("Thread?Name"));
+ Assert.That(logEventXml, Does.Contain("Do?main"));
+ Assert.That(logEventXml, Does.Contain("Ident?ity"));
+ Assert.That(logEventXml, Does.Contain("User?Name"));
+ Assert.That(logEventXml, Does.Contain("Mess?age"));
+ Assert.That(logEventXml, Does.Contain("oh?my"));
+
+ void Log()
+ {
+ // Build a LoggingEvent with an XML invalid character in a property value
+ LoggingEventData data = new()
+ {
+ LoggerName = "Log\u0001ger",
+ Level = Level.Info,
+ TimeStampUtc = DateTime.UtcNow,
+ ThreadName = "Thread\u0001Name",
+ Domain = "Do\u0001main",
+ Identity = "Ident\u0001ity",
+ UserName = "User\u0001Name",
+ Message = "Mess\u0001age",
+ ExceptionString = "oh\u0001my",
+ Properties = new()
+ };
+
+ // Value contains U+0001 which is illegal in XML 1.0
+ data.Properties["us\u0001er"] = "A\u0001B";
+
+ // Log the event
+ log.Logger.Log(new(null, repository, data));
+ }
+ }
}
\ No newline at end of file
diff --git a/src/log4net.Tests/Util/TransformTest.cs
b/src/log4net.Tests/Util/TransformTest.cs
index 78b106cd..b0e932ac 100644
--- a/src/log4net.Tests/Util/TransformTest.cs
+++ b/src/log4net.Tests/Util/TransformTest.cs
@@ -40,4 +40,4 @@ public void MaskXmlInvalidCharactersMasks0Char()
const string c = "\0";
Assert.That(Transform.MaskXmlInvalidCharacters(c, "?"), Is.EqualTo("?"));
}
-}
+}
\ No newline at end of file
diff --git a/src/log4net/Layout/Internal/XmlWriterExtensions.cs
b/src/log4net/Layout/Internal/XmlWriterExtensions.cs
index e08b759d..91c46070 100644
--- a/src/log4net/Layout/Internal/XmlWriterExtensions.cs
+++ b/src/log4net/Layout/Internal/XmlWriterExtensions.cs
@@ -32,10 +32,11 @@ namespace log4net.Layout.Internal;
internal static partial class XmlWriterExtensions
{
#if NETSTANDARD2_0_OR_GREATER
- private static readonly XmlWriterSettings _settings = new XmlWriterSettings
+ private static readonly XmlWriterSettings _settings = new()
{
Indent = false,
- OmitXmlDeclaration = true
+ OmitXmlDeclaration = true,
+ CheckCharacters = false
};
#endif
@@ -71,4 +72,14 @@ internal static XmlWriter CreateXmlWriter(TextWriter writer)
Namespaces = false
};
#endif
+
+ /// <summary>
+ /// writes the attribute and replaces invalid characters
+ /// </summary>
+ internal static void WriteAttributeStringSafe(this XmlWriter writer, string
localName, string? value, string mask)
+ {
+ if (string.IsNullOrEmpty(value))
+ return;
+ writer.WriteAttributeString(localName,
Transform.MaskXmlInvalidCharacters(value!, mask));
+ }
}
\ No newline at end of file
diff --git a/src/log4net/Layout/XmlLayout.cs b/src/log4net/Layout/XmlLayout.cs
index 0233808c..90b59c1c 100644
--- a/src/log4net/Layout/XmlLayout.cs
+++ b/src/log4net/Layout/XmlLayout.cs
@@ -192,28 +192,30 @@ public override void ActivateOptions()
protected override void FormatXml(XmlWriter writer, LoggingEvent
loggingEvent)
{
writer.EnsureNotNull().WriteStartElement(_eventElementName, Prefix,
DefaultEventElementName, Prefix);
- writer.WriteAttributeString(LoggerAttributeName,
loggingEvent.EnsureNotNull().LoggerName!);
+ writer.WriteAttributeStringSafe(LoggerAttributeName,
loggingEvent.EnsureNotNull().LoggerName, InvalidCharReplacement);
- writer.WriteAttributeString(TimestampAttributeName,
XmlConvert.ToString(loggingEvent.TimeStamp,
XmlDateTimeSerializationMode.Local));
+ writer.WriteAttributeStringSafe(TimestampAttributeName,
+ XmlConvert.ToString(loggingEvent.TimeStamp,
XmlDateTimeSerializationMode.Local),
+ InvalidCharReplacement);
if (loggingEvent.Level is not null)
{
- writer.WriteAttributeString(LevelAttributeName,
loggingEvent.Level.DisplayName);
+ writer.WriteAttributeStringSafe(LevelAttributeName,
loggingEvent.Level.DisplayName, InvalidCharReplacement);
}
- writer.WriteAttributeString(ThreadAttributeName, loggingEvent.ThreadName!);
+ writer.WriteAttributeStringSafe(ThreadAttributeName,
loggingEvent.ThreadName!, InvalidCharReplacement);
if (loggingEvent.Domain is not null && loggingEvent.Domain.Length > 0)
{
- writer.WriteAttributeString(DomainAttributeName, loggingEvent.Domain);
+ writer.WriteAttributeStringSafe(DomainAttributeName,
loggingEvent.Domain, InvalidCharReplacement);
}
if (loggingEvent.Identity is not null && loggingEvent.Identity.Length > 0)
{
- writer.WriteAttributeString(IdentityAttributeName,
loggingEvent.Identity);
+ writer.WriteAttributeStringSafe(IdentityAttributeName,
loggingEvent.Identity, InvalidCharReplacement);
}
if (loggingEvent.UserName.Length > 0)
{
- writer.WriteAttributeString(UsernameAttributeName,
loggingEvent.UserName);
+ writer.WriteAttributeStringSafe(UsernameAttributeName,
loggingEvent.UserName, InvalidCharReplacement);
}
// Append the message text
@@ -222,13 +224,13 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
writer.WriteStartElement(_messageElementName, Prefix,
DefaultMessageElementName, Prefix);
if (!Base64EncodeMessage)
{
- Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage,
InvalidCharReplacement);
+ writer.WriteEscapedXmlString(loggingEvent.RenderedMessage,
InvalidCharReplacement);
}
else
{
byte[] messageBytes =
Encoding.UTF8.GetBytes(loggingEvent.RenderedMessage);
string base64Message = Convert.ToBase64String(messageBytes, 0,
messageBytes.Length);
- Transform.WriteEscapedXmlString(writer, base64Message,
InvalidCharReplacement);
+ writer.WriteEscapedXmlString(base64Message, InvalidCharReplacement);
}
writer.WriteEndElement();
}
@@ -242,22 +244,17 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
foreach (KeyValuePair<string, object?> entry in properties)
{
writer.WriteStartElement(_dataElementName, Prefix,
DefaultDataElementName, Prefix);
- writer.WriteAttributeString(NameAttributeName,
Transform.MaskXmlInvalidCharacters(entry.Key, InvalidCharReplacement));
+ writer.WriteAttributeStringSafe(NameAttributeName, entry.Key,
InvalidCharReplacement);
// Use an ObjectRenderer to convert the object to a string
if (loggingEvent.Repository is not null)
{
- string valueStr;
- if (!Base64EncodeProperties)
+ string valueStr =
loggingEvent.Repository.RendererMap.FindAndRender(entry.Value);
+ if (Base64EncodeProperties)
{
- valueStr =
Transform.MaskXmlInvalidCharacters(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value),
InvalidCharReplacement);
+ valueStr =
Convert.ToBase64String(Encoding.UTF8.GetBytes(valueStr));
}
- else
- {
- byte[] propertyValueBytes =
Encoding.UTF8.GetBytes(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value));
- valueStr = Convert.ToBase64String(propertyValueBytes, 0,
propertyValueBytes.Length);
- }
- writer.WriteAttributeString(ValueAttributeName, valueStr);
+ writer.WriteAttributeStringSafe(ValueAttributeName, valueStr,
InvalidCharReplacement);
}
writer.WriteEndElement();
@@ -270,7 +267,7 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
{
// Append the stack trace line
writer.WriteStartElement(_exceptionElementName, Prefix,
DefaultExceptionElementName, Prefix);
- Transform.WriteEscapedXmlString(writer, exceptionStr,
InvalidCharReplacement);
+ writer.WriteEscapedXmlString(exceptionStr, InvalidCharReplacement);
writer.WriteEndElement();
}
@@ -279,10 +276,10 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
if (loggingEvent.LocationInformation is LocationInfo locationInfo)
{
writer.WriteStartElement(_locationElementName, Prefix,
DefaultLocationElementName, Prefix);
- writer.WriteAttributeString(ClassAttributeName,
locationInfo.ClassName!);
- writer.WriteAttributeString(MethodAttributeName,
locationInfo.MethodName);
- writer.WriteAttributeString(FileAttributeName, locationInfo.FileName!);
- writer.WriteAttributeString(LineAttributeName,
locationInfo.LineNumber);
+ writer.WriteAttributeStringSafe(ClassAttributeName,
locationInfo.ClassName, InvalidCharReplacement);
+ writer.WriteAttributeStringSafe(MethodAttributeName,
locationInfo.MethodName, InvalidCharReplacement);
+ writer.WriteAttributeStringSafe(FileAttributeName,
locationInfo.FileName, InvalidCharReplacement);
+ writer.WriteAttributeStringSafe(LineAttributeName,
locationInfo.LineNumber, InvalidCharReplacement);
writer.WriteEndElement();
}
}
diff --git a/src/log4net/Layout/XmlLayoutSchemaLog4j.cs
b/src/log4net/Layout/XmlLayoutSchemaLog4j.cs
index ff2df82f..c8f1636f 100644
--- a/src/log4net/Layout/XmlLayoutSchemaLog4j.cs
+++ b/src/log4net/Layout/XmlLayoutSchemaLog4j.cs
@@ -47,8 +47,7 @@ public class XmlLayoutSchemaLog4J : XmlLayoutBase
/// Constructs an XMLLayoutSchemaLog4j
/// </summary>
public XmlLayoutSchemaLog4J()
- {
- }
+ { }
/// <summary>
/// Constructs an XMLLayoutSchemaLog4j.
@@ -67,9 +66,9 @@ public XmlLayoutSchemaLog4J()
/// appender as well.
/// </para>
/// </remarks>
- public XmlLayoutSchemaLog4J(bool locationInfo) : base(locationInfo)
- {
- }
+ public XmlLayoutSchemaLog4J(bool locationInfo)
+ : base(locationInfo)
+ { }
/// <summary>
/// The version of the log4j schema to use.
@@ -137,8 +136,7 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
}
// translate appdomain name
- if (loggingEvent.LookupProperty("log4japp") is null
- && loggingEvent.Domain?.Length > 0)
+ if (loggingEvent.LookupProperty("log4japp") is null &&
loggingEvent.Domain?.Length > 0)
{
loggingEvent.GetProperties()["log4japp"] = loggingEvent.Domain;
}
@@ -159,7 +157,7 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
// Write the start element
writer.EnsureNotNull().WriteStartElement("log4j:event", "log4j", "event",
"log4net");
- writer.WriteAttributeString("logger", loggingEvent.LoggerName);
+ writer.WriteAttributeStringSafe("logger", loggingEvent.LoggerName,
InvalidCharReplacement);
// Calculate the timestamp as the number of milliseconds since january 1970
//
@@ -168,18 +166,18 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
// caused by daylight savings time transitions.
TimeSpan timeSince1970 = loggingEvent.TimeStampUtc - _sDate1970;
- writer.WriteAttributeString("timestamp",
XmlConvert.ToString((long)timeSince1970.TotalMilliseconds));
+ writer.WriteAttributeStringSafe("timestamp",
XmlConvert.ToString((long)timeSince1970.TotalMilliseconds),
InvalidCharReplacement);
if (loggingEvent.Level is not null)
{
- writer.WriteAttributeString("level", loggingEvent.Level.DisplayName);
+ writer.WriteAttributeStringSafe("level", loggingEvent.Level.DisplayName,
InvalidCharReplacement);
}
- writer.WriteAttributeString("thread", loggingEvent.ThreadName);
+ writer.WriteAttributeStringSafe("thread", loggingEvent.ThreadName,
InvalidCharReplacement);
// Append the message text
if (loggingEvent.RenderedMessage is not null)
{
writer.WriteStartElement("log4j:message", "log4j", "message", "log4net");
- Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage,
InvalidCharReplacement);
+ writer.WriteEscapedXmlString(loggingEvent.RenderedMessage,
InvalidCharReplacement);
writer.WriteEndElement();
}
@@ -190,7 +188,7 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
{
// Append the NDC text
writer.WriteStartElement("log4j:NDC", "log4j", "NDC", "log4net");
- Transform.WriteEscapedXmlString(writer, valueStr!,
InvalidCharReplacement);
+ writer.WriteEscapedXmlString(valueStr!, InvalidCharReplacement);
writer.WriteEndElement();
}
}
@@ -203,13 +201,13 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
foreach (KeyValuePair<string, object?> entry in properties)
{
writer.WriteStartElement("log4j:data", "log4j", "data", "log4net");
- writer.WriteAttributeString("name", entry.Key);
+ writer.WriteAttributeStringSafe("name", entry.Key,
InvalidCharReplacement);
// Use an ObjectRenderer to convert the object to a string
string? valueStr =
loggingEvent.Repository?.RendererMap.FindAndRender(entry.Value);
if (!string.IsNullOrEmpty(valueStr))
{
- writer.WriteAttributeString("value", valueStr);
+ writer.WriteAttributeStringSafe("value", valueStr,
InvalidCharReplacement);
}
writer.WriteEndElement();
@@ -222,7 +220,7 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
{
// Append the stack trace line
writer.WriteStartElement("log4j:throwable", "log4j", "throwable",
"log4net");
- Transform.WriteEscapedXmlString(writer, exceptionStr!,
InvalidCharReplacement);
+ writer.WriteEscapedXmlString(exceptionStr!, InvalidCharReplacement);
writer.WriteEndElement();
}
@@ -231,10 +229,10 @@ protected override void FormatXml(XmlWriter writer,
LoggingEvent loggingEvent)
if (loggingEvent.LocationInformation is LocationInfo locationInfo)
{
writer.WriteStartElement("log4j:locationInfo", "log4j",
"locationInfo", "log4net");
- writer.WriteAttributeString("class", locationInfo.ClassName);
- writer.WriteAttributeString("method", locationInfo.MethodName);
- writer.WriteAttributeString("file", locationInfo.FileName);
- writer.WriteAttributeString("line", locationInfo.LineNumber);
+ writer.WriteAttributeStringSafe("class", locationInfo.ClassName,
InvalidCharReplacement);
+ writer.WriteAttributeStringSafe("method", locationInfo.MethodName,
InvalidCharReplacement);
+ writer.WriteAttributeStringSafe("file", locationInfo.FileName,
InvalidCharReplacement);
+ writer.WriteAttributeStringSafe("line", locationInfo.LineNumber,
InvalidCharReplacement);
writer.WriteEndElement();
}
}
diff --git a/src/log4net/Util/Transform.cs b/src/log4net/Util/Transform.cs
index 6ff80086..b646974b 100644
--- a/src/log4net/Util/Transform.cs
+++ b/src/log4net/Util/Transform.cs
@@ -26,9 +26,6 @@ namespace log4net.Util;
/// Utility class for transforming strings.
/// </summary>
/// <remarks>
-/// <para>
-/// Utility class for transforming strings.
-/// </para>
/// </remarks>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
@@ -46,7 +43,7 @@ public static class Transform
/// or using CDATA sections.
/// </para>
/// </remarks>
- public static void WriteEscapedXmlString(XmlWriter writer, string textData,
string invalidCharReplacement)
+ public static void WriteEscapedXmlString(this XmlWriter writer, string
textData, string invalidCharReplacement)
{
writer.EnsureNotNull();
string stringData = MaskXmlInvalidCharacters(textData,
invalidCharReplacement);