Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Added email validation to CaptureFeedback methods
Resolves #4246
- #4246
  • Loading branch information
jamescrosswell committed Jun 16, 2025
commit a325003809c8b312333ecab4a5355a37236fbb3c
41 changes: 41 additions & 0 deletions src/Sentry/Internal/EmailValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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 EmailRegex { 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;
}

#if NET9_0_OR_GREATER
return EmailRegex.IsMatch(email);
#else
return Email.IsMatch(email);
#endif
}
}
18 changes: 18 additions & 0 deletions src/Sentry/Internal/Hub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,12 @@ public void CaptureFeedback(SentryFeedback feedback, Action<Scope> configureScop

try
{
if (!EmailValidator.IsValidEmail(feedback.ContactEmail))
{
_options.LogWarning("Feedback dropped due to invalid email format: '{0}'", feedback.ContactEmail);
return;
}

var clonedScope = CurrentScope.Clone();
configureScope(clonedScope);

Expand All @@ -573,6 +579,12 @@ public void CaptureFeedback(SentryFeedback feedback, Scope? scope = null, Sentry

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

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

try
{
if (!string.IsNullOrWhiteSpace(userFeedback.Email) && !EmailValidator.IsValidEmail(userFeedback.Email))
{
_options.LogWarning("User feedback dropped due to invalid email format: '{0}'", userFeedback.Email);
return;
}

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

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,102 @@ 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.Client.DidNotReceive().CaptureFeedback(Arg.Any<SentryFeedback>(), Arg.Any<Scope>(), Arg.Any<SentryHint>());
_fixture.Options.DiagnosticLogger.Received(1).Log(
SentryLevel.Warning,
Arg.Is<string>(s => s.Contains("invalid email format")),
null,
Arg.Any<object[]>());
}

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

#if NET6_0_OR_GREATER
Expand Down
Loading