Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/Sentry/Protocol/Envelopes/Envelope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,10 @@ internal async Task SerializeAsync(
{
try
{
await item.SerializeAsync(stream, logger, cancellationToken).ConfigureAwait(false);
await stream.WriteNewlineAsync(cancellationToken).ConfigureAwait(false);
if (await item.SerializeAsync(stream, logger, dropEmptyAttachment: true, cancellationToken).ConfigureAwait(false))
{
await stream.WriteNewlineAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (Exception e)
{
Expand All @@ -180,8 +182,10 @@ internal void Serialize(Stream stream, IDiagnosticLogger? logger, ISystemClock c
{
try
{
item.Serialize(stream, logger);
stream.WriteNewline();
if (item.Serialize(stream, logger, dropEmptyAttachment: true))
{
stream.WriteNewline();
}
}
catch (Exception e)
{
Expand Down
34 changes: 32 additions & 2 deletions src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ private static void SerializeHeader(
}

/// <inheritdoc />
public async Task SerializeAsync(Stream stream, IDiagnosticLogger? logger,
public Task SerializeAsync(Stream stream, IDiagnosticLogger? logger,
CancellationToken cancellationToken = default) =>
SerializeAsync(stream, logger, dropEmptyAttachment: false, cancellationToken);

internal async Task<bool> SerializeAsync(Stream stream, IDiagnosticLogger? logger, bool dropEmptyAttachment,
CancellationToken cancellationToken = default)
{
// Always calculate the length of the payload, as Sentry will reject envelopes that have incorrect lengths
Expand All @@ -189,6 +193,11 @@ public async Task SerializeAsync(Stream stream, IDiagnosticLogger? logger,
await using (payloadBuffer.ConfigureAwait(false))
#endif
{
if (dropEmptyAttachment && IsEmptyAttachmentOfUnknownLength(payloadBuffer, logger))
{
return false;
}

// Write to the outbound stream asynchronously. It's likely either an HttpRequestStream or a FileStream.

// Header
Expand All @@ -199,18 +208,27 @@ public async Task SerializeAsync(Stream stream, IDiagnosticLogger? logger,

// Payload
await payloadBuffer.CopyToAsync(stream, cancellationToken).ConfigureAwait(false);
return true;
}
}

/// <inheritdoc />
public void Serialize(Stream stream, IDiagnosticLogger? logger)
public void Serialize(Stream stream, IDiagnosticLogger? logger) =>
Serialize(stream, logger, dropEmptyAttachment: false);

internal bool Serialize(Stream stream, IDiagnosticLogger? logger, bool dropEmptyAttachment)
{
// Always calculate the length of the payload, as Sentry will reject envelopes that have incorrect lengths
// in item headers. Don't trust any previously calculated value to be correct.
// See https://github.com/getsentry/sentry-dotnet/issues/1956

using var payloadBuffer = BufferPayload(logger);

if (dropEmptyAttachment && IsEmptyAttachmentOfUnknownLength(payloadBuffer, logger))
{
return false;
}

// Header
var headerWithLength = Header.ToDict();
headerWithLength[LengthKey] = payloadBuffer.Length;
Expand All @@ -219,6 +237,18 @@ public void Serialize(Stream stream, IDiagnosticLogger? logger)

// Payload
payloadBuffer.CopyTo(stream);
return true;
}

private bool IsEmptyAttachmentOfUnknownLength(MemoryStream payloadBuffer, IDiagnosticLogger? logger)
{
if (payloadBuffer.Length != 0 || TryGetLength() is not null || Payload is not BufferedStreamSerializable)
{
return false;
}

logger?.LogWarning("Did not add '{0}' to envelope because the attachment was empty.", TryGetFileName());
return true;
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,70 @@ public void FromEvent_EmptyByteAttachment_DoesNotAddAttachment()
.Should()
.Be(0);
}

[Fact]
public async Task Serialization_EmptyNonSeekableStreamAttachment_DropsAttachment()
{
// Arrange
using var envelope = CreateEventEnvelopeWithEmptyNonSeekableAttachment();

// Act
var serialized = await envelope.SerializeToStringAsync(_testOutputLogger, _fakeClock);

// Assert
serialized.Should().NotContain("empty.jpg").And.NotContain("\n\n");
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(serialized));
using var deserialized = await Envelope.DeserializeAsync(stream);
deserialized.Items.Should().ContainSingle()
.Which.TryGetType().Should().Be(EnvelopeItem.TypeValueEvent);
}

[Fact]
public async Task Serialization_EmptyNonSeekableStreamAttachmentSynchronously_DropsAttachment()
{
// Arrange
using var envelope = CreateEventEnvelopeWithEmptyNonSeekableAttachment();

// Act
var serialized = envelope.SerializeToString(_testOutputLogger, _fakeClock);

// Assert
serialized.Should().NotContain("empty.jpg").And.NotContain("\n\n");
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(serialized));
using var deserialized = await Envelope.DeserializeAsync(stream);
deserialized.Items.Should().ContainSingle()
.Which.TryGetType().Should().Be(EnvelopeItem.TypeValueEvent);
}

[Fact]
public async Task Serialization_EmptyAttachmentOfKnownLengthAfterRoundTrip_KeepsAttachment()
{
// Arrange
var attachment = new SentryAttachment(
AttachmentType.Default,
new StreamAttachmentContent(new MemoryStream()),
"empty.log",
"text/plain");
using var envelope = Envelope.FromAttachment(SentryId.Create(), attachment, _testOutputLogger);
using var cached = new MemoryStream(Encoding.UTF8.GetBytes(
await envelope.SerializeToStringAsync(_testOutputLogger, _fakeClock)));
using var deserialized = await Envelope.DeserializeAsync(cached);

// Act
var serialized = await deserialized.SerializeToStringAsync(_testOutputLogger, _fakeClock);

// Assert
serialized.Should().Contain("empty.log");
}

private Envelope CreateEventEnvelopeWithEmptyNonSeekableAttachment()
{
var attachment = new SentryAttachment(
AttachmentType.Default,
new StreamAttachmentContent(new NonSeekableReadStream(new MemoryStream())),
"empty.jpg",
"image/jpeg");

return Envelope.FromEvent(new SentryEvent(), _testOutputLogger, [attachment]);
}
}
Loading