diff --git a/Ical.Net.Tests/SerializationTests.cs b/Ical.Net.Tests/SerializationTests.cs
index 5919b5a5..1c101101 100644
--- a/Ical.Net.Tests/SerializationTests.cs
+++ b/Ical.Net.Tests/SerializationTests.cs
@@ -7,6 +7,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -611,4 +612,35 @@ public void TestConvertToInt32WithNegativeNumberInDifferentCultures(string cultu
}
}
+ [Test]
+ public void CalendarSerialization_ShouldDefaultTo_Utf8NoBom()
+ {
+ var calendar = new Calendar();
+ calendar.Events.Add(new CalendarEvent
+ {
+ Summary = "Sample Event",
+ DtStart = new CalDateTime(2025, 6, 10, 9, 0, 0),
+ DtEnd = new CalDateTime(2025, 6, 10, 10, 0, 0)
+ });
+
+ // Serialize with default encoding (should be UTF-8 without BOM)
+ using var msNoBom = new MemoryStream();
+ new CalendarSerializer().Serialize(calendar, msNoBom);
+ var noBomBytes = msNoBom.ToArray();
+
+ // Serialize with explicit UTF-8 BOM
+ using var msWithBom = new MemoryStream();
+ new CalendarSerializer().Serialize(calendar, msWithBom, new UTF8Encoding(true));
+ var withBomBytes = msWithBom.ToArray();
+
+ // UTF-8 BOM is 0xEF,0xBB,0xBF
+ bool HasBom(byte[] bytes) =>
+ bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF;
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(HasBom(noBomBytes), Is.False, "Stream should not contain a UTF-8 BOM");
+ Assert.That(HasBom(withBomBytes), Is.True, "Stream should contain a UTF-8 BOM");
+ });
+ }
}
diff --git a/Ical.Net/Serialization/ISerializer.cs b/Ical.Net/Serialization/ISerializer.cs
index d6c5f1f1..1a25cbfb 100644
--- a/Ical.Net/Serialization/ISerializer.cs
+++ b/Ical.Net/Serialization/ISerializer.cs
@@ -14,6 +14,6 @@ public interface ISerializer : IServiceProvider
SerializationContext SerializationContext { get; set; }
Type TargetType { get; }
- void Serialize(object obj, Stream stream, Encoding encoding);
+ void Serialize(object obj, Stream stream, Encoding? encoding = null);
object? Deserialize(Stream stream, Encoding encoding);
}
diff --git a/Ical.Net/Serialization/SerializerBase.cs b/Ical.Net/Serialization/SerializerBase.cs
index 0960293a..4b2b24c6 100644
--- a/Ical.Net/Serialization/SerializerBase.cs
+++ b/Ical.Net/Serialization/SerializerBase.cs
@@ -43,13 +43,28 @@ protected SerializerBase(SerializationContext ctx)
return obj;
}
- public void Serialize(object obj, Stream stream, Encoding encoding)
+ ///
+ /// Serializes the specified object to the provided stream using the specified encoding.
+ ///
+ /// This method writes the serialized representation of the object to the stream without closing
+ /// the stream, allowing the caller to continue using it.
+ /// bytes.
+ /// The object to serialize. Must not be null.
+ /// The stream to which the serialized data will be written. Must be writable.
+ ///
+ /// The character encoding to use for serialization.
+ /// If or missing, UTF-8 encoding
+ /// without a byte order mark (BOM) is used.
+ /// A BOM is incompatible with many iCalendar apps.
+ ///
+ public void Serialize(object obj, Stream stream, Encoding? encoding = null)
{
- // NOTE: we don't use a 'using' statement here because
- // we don't want the stream to be closed by this serialization.
- // Fixes bug #3177278 - Serialize closes stream
+ // Ensure that no BOM is written to the stream
+ encoding ??= new UTF8Encoding(false);
- const int defaultBuffer = 1024; //This is StreamWriter's built-in default buffer size
+ //This is StreamWriter's built-in default buffer size
+ const int defaultBuffer = 1024;
+ // Important: leave the stream open so that the caller can continue to use it
using var sw = new StreamWriter(stream, encoding, defaultBuffer, leaveOpen: true);
// Push the current object onto the serialization stack