| File: Internal\InternalMicrosoftDetectionUtilities.cs | Web Access |
| Project: Microsoft.DotNet.Cli.InternalMicrosoft.csproj (Microsoft.DotNet.Cli.InternalMicrosoft) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json; using System.Text.RegularExpressions; namespace Microsoft.DotNet.Cli.InternalMicrosoft; /// <summary> /// Provides shared validation, normalization, and safe failure conversion for detection providers. /// The utility holds no process state. /// </summary> internal static partial class InternalMicrosoftDetectionUtilities { internal const string MicrosoftAccountDomain = "microsoft.com"; internal const string MicrosoftLoginHost = "login.microsoftonline.com"; internal const string MicrosoftTenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47"; private const string CorpMicrosoftDomainSuffix = ".corp.microsoft.com"; private const string MicrosoftAccountDomainSuffix = "." + MicrosoftAccountDomain; internal static bool IsWsl(string? distroName, string? interop, string? kernelRelease) => !string.IsNullOrEmpty(distroName) || !string.IsNullOrEmpty(interop) || kernelRelease?.Contains("microsoft", StringComparison.OrdinalIgnoreCase) == true; internal static bool TryGetCorporateDomain(string? value, out string? domain) { domain = null; if (string.IsNullOrWhiteSpace(value)) { return false; } var trimmed = value.Trim().TrimEnd('.'); if (!trimmed.EndsWith(CorpMicrosoftDomainSuffix, StringComparison.OrdinalIgnoreCase)) { return false; } domain = NormalizeDomain(trimmed[..^CorpMicrosoftDomainSuffix.Length]); return domain is not null; } internal static string? NormalizeAlias(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } var trimmed = value.Trim(); return AliasRegex().IsMatch(trimmed) ? trimmed.ToLowerInvariant() : null; } internal static string? NormalizeDomain(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } var trimmed = value.Trim(); return DomainRegex().IsMatch(trimmed) ? trimmed.ToUpperInvariant() : null; } internal static bool TryGetMicrosoftAccountIdentity( string? value, out string? alias, out string? domain) { alias = null; domain = null; if (string.IsNullOrWhiteSpace(value)) { return false; } var separator = value.IndexOf('@'); if (separator <= 0 || separator == value.Length - 1) { return false; } var suffix = value[(separator + 1)..]; if (!suffix.Equals(MicrosoftAccountDomain, StringComparison.OrdinalIgnoreCase) && !suffix.EndsWith(MicrosoftAccountDomainSuffix, StringComparison.OrdinalIgnoreCase)) { return false; } alias = NormalizeAlias(value[..separator]); domain = TryGetCorporateDomain(suffix, out var corporateDomain) ? corporateDomain : null; return alias is not null; } internal static bool IsGitHubToken(string? value) => !string.IsNullOrWhiteSpace(value) && GitHubTokenRegex().IsMatch(value.Trim()); internal static string? GetEnvironmentVariableFromSetOutput(string output, string variableName) { var prefix = variableName + "="; foreach (var line in output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)) { if (line.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { return line[prefix.Length..]; } } return null; } internal static bool TryGetString(JsonElement element, string propertyName, out string value) { value = string.Empty; return element.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String && (value = property.GetString() ?? string.Empty).Length > 0; } internal static InternalMicrosoftProbeFailure CreateExceptionFailure(Exception exception, string stage) => new( InternalMicrosoftProbeFailureCode.Exception, stage, ExceptionType: GetSafeExceptionType(exception)); internal static IReadOnlyList<string> ReadGitHubTokensFromJsonFile(string path) { if (!File.Exists(path)) { return []; } try { return GitHubTokenValueRegex() .Matches(File.ReadAllText(path)) .Select(match => match.Groups["token"].Value) .ToArray(); } catch (IOException) { return []; } catch (UnauthorizedAccessException) { return []; } } private static string? GetSafeExceptionType(Exception exception) => exception switch { HttpRequestException => nameof(HttpRequestException), IOException => nameof(IOException), JsonException => nameof(JsonException), UnauthorizedAccessException => nameof(UnauthorizedAccessException), InvalidOperationException => nameof(InvalidOperationException), _ => null }; [GeneratedRegex("^[A-Za-z0-9._-]+$", RegexOptions.CultureInvariant)] private static partial Regex AliasRegex(); [GeneratedRegex("^[A-Za-z0-9._-]+$", RegexOptions.CultureInvariant)] private static partial Regex DomainRegex(); [GeneratedRegex("^(gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})$", RegexOptions.CultureInvariant)] private static partial Regex GitHubTokenRegex(); [GeneratedRegex(@"(?<![A-Za-z0-9_])(?<token>gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})(?![A-Za-z0-9_])", RegexOptions.CultureInvariant)] private static partial Regex GitHubTokenValueRegex(); }