Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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, a warning is now logged if Debug mode is enabled (explaining why the feedback has been dropped) ([#4284](https://github.com/getsentry/sentry-dotnet/pull/4284))

### Dependencies

Expand Down
37 changes: 37 additions & 0 deletions src/Sentry/Internal/EmailValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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)
{
if (string.IsNullOrEmpty(email))
{
return true;
}

return 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
Loading