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
33 changes: 33 additions & 0 deletions Semantics.Strings/Validation/Attributes/Text/IsBase64Attribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ protected override ValidationResult ValidateValue(string value)
return ValidationResult.Failure("The value must be a valid Base64 string.");
}

// Convert.FromBase64String skips whitespace, which the length check above counts, so a
// value with whitespace would pass whenever it made the length a multiple of 4. Only the
// Base64 alphabet is accepted, with at most two '=' and only at the end.
if (!HasBase64Alphabet(value))
{
return ValidationResult.Failure("The value must be a valid Base64 string.");
}

// Check for valid Base64 characters and proper padding
try
{
Expand All @@ -50,5 +58,30 @@ protected override ValidationResult ValidateValue(string value)
return ValidationResult.Failure("The value must be a valid Base64 string.");
}
}

private static bool HasBase64Alphabet(string value)
{
int dataLength = value.Length;
while (dataLength > 0 && value[dataLength - 1] == '=')
{
dataLength--;
}

if (value.Length - dataLength > 2)
{
return false;
}

for (int i = 0; i < dataLength; i++)
{
char c = value[i];
if (c is not ((>= 'A' and <= 'Z') or (>= 'a' and <= 'z') or (>= '0' and <= '9') or '+' or '/'))
{
return false;
}
}

return true;
}
}
}
26 changes: 24 additions & 2 deletions Semantics.Test/TextValidationAttributesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,31 @@ public void IsBase64Attribute_InvalidBase64_ShouldThrow()
}

[TestMethod]
public void IsBase64Attribute_Base64WithSpaces_ShouldThrow()
[DataRow("SGVs bG8g V29y bGQ=")]
[DataRow("SGVs bG8g V29y bGQ= ")]
[DataRow("SGVsbG8gV29ybGQ= ")]
[DataRow("SGVsbG8g\r\nV29ybGQ=\r\n")]
[DataRow("SGVsbG8gV29ybGQ=\t\t\t\t")]
public void IsBase64Attribute_Base64WithWhitespace_ShouldThrow(string value)
{
Assert.ThrowsExactly<ArgumentException>(() => SemanticString<Base64String>.Create<Base64String>("SGVs bG8g V29y bGQ="));
Assert.ThrowsExactly<ArgumentException>(() => SemanticString<Base64String>.Create<Base64String>(value));
}

[TestMethod]
[DataRow("VGVzdA=a")]
[DataRow("VGV=dA==")]
[DataRow("VGVzd===")]
public void IsBase64Attribute_MisplacedPadding_ShouldThrow(string value)
{
Assert.ThrowsExactly<ArgumentException>(() => SemanticString<Base64String>.Create<Base64String>(value));
}

[TestMethod]
[DataRow("+/+/")]
[DataRow("AB+/CD==")]
public void IsBase64Attribute_PlusAndSlash_ShouldPass(string value)
{
Assert.AreEqual(value, SemanticString<Base64String>.Create<Base64String>(value).WeakString);
}

[TestMethod]
Expand Down
Loading