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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

- The HTTP instrumentation uses the span created for the outgoing request in the sentry-trace header, fixing the parent-child relationship between client and server ([#4264](https://github.com/getsentry/sentry-dotnet/pull/4264))
- InvalidOperationException sending attachments on Android with LLVM enabled ([#4276](https://github.com/getsentry/sentry-dotnet/pull/4276))
- When CaptureFeedback methods are called with invalid email addresses, the email address will be removed and, if Debug mode is enabled, a warning will be logged. This is done to avoid losing the Feedback altogether (Sentry would reject Feedback that has an invalid email address) ([#4284](https://github.com/getsentry/sentry-dotnet/pull/4284))

### Dependencies

Expand Down
29 changes: 29 additions & 0 deletions src/Sentry/Internal/EmailValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Text.RegularExpressions;

namespace Sentry.Internal;

/// <summary>
/// Helper class for email validation.
/// </summary>
internal static partial class EmailValidator
{
private const string EmailPattern = @"^[^@\s]+@[^@\s]+\.[^@\s]+$";

#if NET9_0_OR_GREATER
[GeneratedRegex(EmailPattern)]
private static partial Regex Email { get; }
#elif NET8_0
[GeneratedRegex(EmailPattern)]
private static partial Regex EmailRegex();
private static readonly Regex Email = EmailRegex();
#else
private static readonly Regex Email = new(EmailPattern, RegexOptions.Compiled);
#endif

/// <summary>
/// Validates an email address.
/// </summary>
/// <param name="email">The email address to validate.</param>
/// <returns>True if the email is valid, false otherwise.</returns>
public static bool IsValidEmail(string email) => Email.IsMatch(email);
}
16 changes: 16 additions & 0 deletions src/Sentry/Internal/Hub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,12 @@ public void CaptureFeedback(SentryFeedback feedback, Scope? scope = null, Sentry

try
{
if (!string.IsNullOrWhiteSpace(feedback.ContactEmail) && !EmailValidator.IsValidEmail(feedback.ContactEmail))
{
_options.LogWarning("Feedback email scrubbed due to invalid email format: '{0}'", feedback.ContactEmail);
feedback.ContactEmail = null;
}

scope ??= CurrentScope;
CurrentClient.CaptureFeedback(feedback, scope, hint);
}
Expand Down Expand Up @@ -620,6 +626,16 @@ public void CaptureUserFeedback(UserFeedback userFeedback)

try
{
if (!string.IsNullOrWhiteSpace(userFeedback.Email) && !EmailValidator.IsValidEmail(userFeedback.Email))
{
_options.LogWarning("Feedback email scrubbed due to invalid email format: '{0}'", userFeedback.Email);
userFeedback = new UserFeedback(
userFeedback.EventId,
userFeedback.Name,
null, // Scrubbed email
userFeedback.Comments);
}

CurrentClient.CaptureUserFeedback(userFeedback);
}
catch (Exception e)
Expand Down
100 changes: 99 additions & 1 deletion test/Sentry.Tests/HubTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.IO.Abstractions.TestingHelpers;
using Sentry.Internal.Http;
using Sentry.Protocol;
using Sentry.Tests.Internals;

namespace Sentry.Tests;
Expand Down Expand Up @@ -1742,7 +1743,7 @@ public void CaptureUserFeedback_HubEnabled(bool enabled)
hub.Dispose();
}

var feedback = new UserFeedback(SentryId.Create(), "foo", "bar", "baz");
var feedback = new UserFeedback(SentryId.Create(), "foo", "bar@example.com", "baz");

// Act
hub.CaptureUserFeedback(feedback);
Expand Down Expand Up @@ -1890,6 +1891,103 @@ await transport.Received(1)
}

private static Scope GetCurrentScope(Hub hub) => hub.ScopeManager.GetCurrent().Key;

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("[email protected]")]
[InlineData("[email protected]")]
[InlineData("[email protected]")]
public void CaptureFeedback_ValidEmail_FeedbackRegistered(string email)
{
// Arrange
var hub = _fixture.GetSut();
var feedback = new SentryFeedback("Test feedback", email);

// Act
hub.CaptureFeedback(feedback);

// Assert
_fixture.Client.Received(1).CaptureFeedback(Arg.Any<SentryFeedback>(), Arg.Any<Scope>(), Arg.Any<SentryHint>());
}

[Theory]
[InlineData("invalid-email")]
[InlineData("missing@domain")]
[InlineData("@missing-local.com")]
[InlineData("spaces [email protected]")]
public void CaptureFeedback_InvalidEmail_FeedbackDropped(string email)
{
// Arrange
_fixture.Options.Debug = true;
_fixture.Options.DiagnosticLogger = Substitute.For<IDiagnosticLogger>();
_fixture.Options.DiagnosticLogger!.IsEnabled(Arg.Any<SentryLevel>()).Returns(true);
var hub = _fixture.GetSut();
var feedback = new SentryFeedback("Test feedback", email);

// Act
hub.CaptureFeedback(feedback);

// Assert
_fixture.Options.DiagnosticLogger.Received(1).Log(
SentryLevel.Warning,
Arg.Is<string>(s => s.Contains("invalid email format")),
null,
Arg.Any<object[]>());
_fixture.Client.Received(1).CaptureFeedback(Arg.Is<SentryFeedback>(f => f.ContactEmail.IsNull()),
Arg.Any<Scope>(), Arg.Any<SentryHint>());
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("[email protected]")]
[InlineData("[email protected]")]
[InlineData("[email protected]")]
public void CaptureUserFeedback_ValidEmail_FeedbackRegistered(string email)
{
#pragma warning disable CS0618 // Type or member is obsolete
// Arrange
var hub = _fixture.GetSut();
var feedback = new UserFeedback(SentryId.Create(), "Test name", email, "Test comment");

// Act
hub.CaptureUserFeedback(feedback);

// Assert
_fixture.Client.Received(1).CaptureUserFeedback(Arg.Any<UserFeedback>());
#pragma warning restore CS0618 // Type or member is obsolete
}

[Theory]
[InlineData("invalid-email")]
[InlineData("missing@domain")]
[InlineData("@missing-local.com")]
[InlineData("spaces [email protected]")]
public void CaptureUserFeedback_InvalidEmail_FeedbackDropped(string email)
{
#pragma warning disable CS0618 // Type or member is obsolete
// Arrange
_fixture.Options.Debug = true;
_fixture.Options.DiagnosticLogger = Substitute.For<IDiagnosticLogger>();
_fixture.Options.DiagnosticLogger!.IsEnabled(Arg.Any<SentryLevel>()).Returns(true);
var hub = _fixture.GetSut();
var feedback = new UserFeedback(SentryId.Create(), "Test name", email, "Test comment");

// Act
hub.CaptureUserFeedback(feedback);

// Assert
_fixture.Options.DiagnosticLogger.Received(1).Log(
SentryLevel.Warning,
Arg.Is<string>(s => s.Contains("invalid email format")),
null,
Arg.Any<object[]>());
_fixture.Client.Received(1).CaptureUserFeedback(Arg.Is<UserFeedback>(f => f.Email.IsNull()));
#pragma warning restore CS0618 // Type or member is obsolete
}
}

#if NET6_0_OR_GREATER
Expand Down