Skip to content

Instantly share code, notes, and snippets.

@juliusl
Created July 20, 2022 01:45
Show Gist options
  • Save juliusl/5f4a3ade5440be1f71ffd531fb7dc401 to your computer and use it in GitHub Desktop.
Save juliusl/5f4a3ade5440be1f71ffd531fb7dc401 to your computer and use it in GitHub Desktop.
Self-Contained NameValidator port
/// <summary>
/// Provides helpers to validate resource names across the Microsoft Azure Storage Services.
/// </summary>
public static class NameValidator
{
/// <summary>
/// Checks if a container name is valid.
/// </summary>
/// <param name="containerName">A string representing the container name to validate.</param>
public static void ValidateContainerName(string containerName)
{
if (!("$root".Equals(containerName, StringComparison.Ordinal) || "$logs".Equals(containerName, StringComparison.Ordinal)))
{
ValidateShareContainerQueueHelper(containerName, Container);
}
}
/// <summary>
/// Checks if a queue name is valid.
/// </summary>
/// <param name="queueName">A string representing the queue name to validate.</param>
public static void ValidateQueueName(string queueName)
{
ValidateShareContainerQueueHelper(queueName, Queue);
}
/// <summary>
/// Checks if a share name is valid.
/// </summary>
/// <param name="shareName">A string representing the share name to validate.</param>
public static void ValidateShareName(string shareName)
{
ValidateShareContainerQueueHelper(shareName, Share);
}
private static void ValidateShareContainerQueueHelper(string resourceName, string resourceType)
{
if (string.IsNullOrWhiteSpace(resourceName))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, ResourceNameEmpty, resourceType));
}
if (resourceName.Length < NameValidator.ContainerShareQueueTableMinLength || resourceName.Length > ContainerShareQueueTableMaxLength)
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, InvalidResourceNameLength, resourceType, ContainerShareQueueTableMinLength, ContainerShareQueueTableMaxLength));
}
if (!ShareContainerQueueRegex.IsMatch(resourceName))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, InvalidResourceName, resourceType));
}
}
/// <summary>
/// Checks if a blob name is valid.
/// </summary>
/// <param name="blobName">A string representing the blob name to validate.</param>
public static void ValidateBlobName(string blobName)
{
if (string.IsNullOrWhiteSpace(blobName))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, ResourceNameEmpty, Blob));
}
if (blobName.Length < BlobFileDirectoryMinLength || blobName.Length > BlobMaxLength)
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, InvalidResourceNameLength, Blob, BlobFileDirectoryMinLength, BlobMaxLength));
}
int slashCount = 0;
foreach (char c in blobName)
{
if (c == '/')
{
slashCount++;
}
}
// 254 slashes means 255 path segments; max 254 segments for blobs, 255 includes container.
if (slashCount >= 254)
{
throw new ArgumentException(TooManyPathSegments);
}
}
/// <summary>
/// Checks if a file name is valid.
/// </summary>
/// <param name="fileName">A string representing the file name to validate.</param>
public static void ValidateFileName(string fileName)
{
ValidateFileDirectoryHelper(fileName, File);
if (fileName.EndsWith("/", StringComparison.Ordinal))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, InvalidResourceName, File));
}
foreach (string s in ReservedFileNames)
{
if (s.Equals(fileName, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, InvalidResourceReservedName, File));
}
}
}
/// <summary>
/// Checks if a directory name is valid.
/// </summary>
/// <param name="directoryName">A string representing the directory name to validate.</param>
public static void ValidateDirectoryName(string directoryName)
{
ValidateFileDirectoryHelper(directoryName, Directory);
}
private static void ValidateFileDirectoryHelper(string resourceName, string resourceType)
{
if (string.IsNullOrWhiteSpace(resourceName))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, ResourceNameEmpty, resourceType));
}
if (resourceName.Length < BlobFileDirectoryMinLength || resourceName.Length > FileDirectoryMaxLength)
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, InvalidResourceNameLength, resourceType, BlobFileDirectoryMinLength, FileDirectoryMaxLength));
}
if (!FileDirectoryRegex.IsMatch(resourceName))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, InvalidResourceName, resourceType));
}
}
/// <summary>
/// Checks if a table name is valid.
/// </summary>
/// <param name="tableName">A string representing the table name to validate.</param>
public static void ValidateTableName(string tableName)
{
if (string.IsNullOrWhiteSpace(tableName))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, ResourceNameEmpty, Table));
}
if (tableName.Length < ContainerShareQueueTableMinLength || tableName.Length > ContainerShareQueueTableMaxLength)
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, InvalidResourceNameLength, Table, ContainerShareQueueTableMinLength, ContainerShareQueueTableMaxLength));
}
if (!(TableRegex.IsMatch(tableName) || MetricsTableRegex.IsMatch(tableName) || tableName.Equals("$MetricsCapacityBlob", StringComparison.OrdinalIgnoreCase)))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, InvalidResourceName, Table));
}
}
#region Private
const int BlobFileDirectoryMinLength = 1;
const int ContainerShareQueueTableMinLength = 3;
const int ContainerShareQueueTableMaxLength = 63;
const int FileDirectoryMaxLength = 255;
const int BlobMaxLength = 1024;
static readonly string[] ReservedFileNames = { ".", "..", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "PRN", "AUX", "NUL", "CON", "CLOCK$" };
static readonly RegexOptions RegexOptions = RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant;
static readonly Regex FileDirectoryRegex = new Regex(@"^[^""\\/:|<>*?]*\/{0,1}$", RegexOptions);
static readonly Regex ShareContainerQueueRegex = new Regex("^[a-z0-9]+(-[a-z0-9]+)*$", RegexOptions);
static readonly Regex TableRegex = new Regex("^[A-Za-z][A-Za-z0-9]*$", RegexOptions);
static readonly Regex MetricsTableRegex = new Regex(@"^\$Metrics(HourPrimary|MinutePrimary|HourSecondary|MinuteSecondary)?(Transactions)(Blob|Queue|Table)$", RegexOptions);
const string InvalidResourceName = "Invalid {0} name. Check MSDN for more information about valid {0} naming.";
const string Table = "table";
const string InvalidResourceNameLength = "Invalid {0} name length. The {0} name must be between {1} and {2} characters long.";
const string ResourceNameEmpty = "Invalid {0} name. The {0} name may not be null, empty, or whitespace only.";
const string File = "file";
const string InvalidResourceReservedName = "Invalid {0} name. This {0} name is reserved.";
const string Share = "share";
const string Directory = "directory";
const string Blob = "blob";
const string Queue = "queue";
const string Container = "container";
const string TooManyPathSegments = "The count of URL path segments (strings between '/' characters) as part of the blob name cannot exceed 254.";
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment