in src/NuGet.Core/NuGet.Versioning/NuGetVersionFactory.cs [131:291]
private static bool TryGetNormalizedVersion(string str, [NotNullWhen(true)] out Version? version)
{
if (string.IsNullOrWhiteSpace(str))
{
version = null;
return false;
}
int minor = 0;
int build = 0;
int revision = 0;
// Check for all the possible parts of the version string. If lastParsedPosition is less than the end of
// the string, the input string was invalid (e.g. "1.2.3.4.5").
bool success = ParseSection(str, 0, out int lastParsedPosition, out int major) &&
ParseSection(str, lastParsedPosition, out lastParsedPosition, out minor) &&
ParseSection(str, lastParsedPosition, out lastParsedPosition, out build) &&
ParseSection(str, lastParsedPosition, out lastParsedPosition, out revision) &&
lastParsedPosition == str.Length;
if (success)
{
version = new Version(major, minor, build, revision);
return true;
}
else
{
version = null;
return false;
}
// Returns false if an invalid section was found while processing the string.
static bool ParseSection(string str, int start, out int end, out int versionNumber)
{
// Section is empty.
if (start == str.Length)
{
end = start;
versionNumber = 0;
return true;
}
// Trim off leading whitespace
for (end = start; end < str.Length; ++end)
{
char currentChar = str[end];
if (!char.IsWhiteSpace(currentChar))
{
if (IsDigit(currentChar))
{
break;
}
else
{
// Found a non-whitespace non-digit character. Invalid string.
versionNumber = 0;
return false;
}
}
}
bool done = false;
bool digitFound = false;
long intermediateVersionNumber = 0;
// Handle number portion.
for (; end < str.Length; ++end)
{
// Negative numbers are invalid for version strings so we only need to check for digits.
char currentChar = str[end];
if (IsDigit(currentChar))
{
// Parse the values digit by digit and multiplies by 10 to make space for the next digit.
// When parsing "123456", this method becomes 1 -> 10 + 2 -> 120 + 3 -> 1230 + 4 -> 12340 + 5 -> 123450 + 6 -> 123456
// We subtract off ASCII value of '0' from our current character to get the digit's value
// e.g. '3' - '0' == 51 - 48 == 3
digitFound = true;
intermediateVersionNumber = intermediateVersionNumber * 10 + currentChar - '0';
// Check for overflow. We can't get outside the bounds of intermediateVersionNumber, a long, before exceeding int.MaxValue
// Intentionally avoid usage of 'checked' statement to avoid exception
if (intermediateVersionNumber > int.MaxValue)
{
versionNumber = 0;
return false;
}
}
else if (currentChar == '.')
{
++end;
// version string ended with '.'
if (end == str.Length)
{
versionNumber = 0;
return false;
}
done = true;
break;
}
else if (char.IsWhiteSpace(currentChar))
{
break;
}
else
{
versionNumber = 0;
return false;
}
}
// We failed to find a number in the section, so the string is invalid.
if (!digitFound)
{
versionNumber = 0;
return false;
}
if (end == str.Length)
{
done = true;
}
if (!done)
{
// trailing whitespace
for (; end < str.Length; ++end)
{
char currentChar = str[end];
if (!char.IsWhiteSpace(currentChar))
{
if (currentChar == '.')
{
++end;
// version string ended with '.'
if (end == str.Length)
{
versionNumber = 0;
return false;
}
break;
}
else
{
versionNumber = 0;
return false;
}
}
}
}
// Previous checks guarantee returnValue <= int.MaxValue
versionNumber = (int)intermediateVersionNumber;
return true;
}
static bool IsDigit(char c)
{
return c >= '0' && c <= '9';
}
}