File: Settings\Settings.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Configuration\NuGet.Configuration.csproj (NuGet.Configuration)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
using NuGet.Common;

namespace NuGet.Configuration
{
    /// <summary>
    /// Concrete implementation of ISettings to support NuGet Settings
    /// Wrapper for computed settings from given settings files
    /// </summary>
    public class Settings : ISettings
    {
        /// <summary>
        /// Default file name for a settings file is 'NuGet.config'
        /// Also, the user level setting file at '%APPDATA%\NuGet' always uses this name
        /// </summary>
        public static readonly string DefaultSettingsFileName = "NuGet.Config";

        /// <summary>
        /// NuGet config names with casing ordered by precedence.
        /// </summary>
        public static readonly string[] OrderedSettingsFileNames =
            PathUtility.IsFileSystemCaseInsensitive ?
            new[] { DefaultSettingsFileName } :
            new[]
            {
                "nuget.config", // preferred style
                "NuGet.config", // Alternative
                DefaultSettingsFileName  // NuGet v2 style
            };

        public static readonly string[] SupportedMachineWideConfigExtension =
            RuntimeEnvironmentHelper.IsWindows ?
            new[] { "*.config" } :
            new[] { "*.Config", "*.config" };

        private readonly Dictionary<string, VirtualSettingSection> _computedSections;

        public SettingSection? GetSection(string sectionName)
        {
            if (_computedSections.TryGetValue(sectionName, out var section))
            {
                return section.Clone() as SettingSection;
            }

            return null;
        }

        public IEnumerable<string> GetAllSettingSections() { return _computedSections.Keys; }

        public void AddOrUpdate(string sectionName, SettingItem item)
        {
            if (string.IsNullOrEmpty(sectionName))
            {
                throw new ArgumentException(Resources.Argument_Cannot_Be_Null_Or_Empty, nameof(sectionName));
            }

            if (item == null)
            {
                throw new ArgumentNullException(nameof(item));
            }

            // Operation is an update
            if (_computedSections.TryGetValue(sectionName, out var section) && section.Items.Contains(item))
            {
                // An update could not be possible here because the operation might be
                // in a machine wide config. If so then we want to add the item to
                // the output config.
                if (section.Update(item))
                {
                    return;
                }
            }

            // Operation is an add
            var outputSettingsFile = GetOutputSettingFileForSection(sectionName);
            if (outputSettingsFile == null)
            {
                throw new InvalidOperationException(Resources.NoWritteableConfig);
            }

            AddOrUpdate(outputSettingsFile, sectionName, item);
        }

        internal void AddOrUpdate(SettingsFile settingsFile, string sectionName, SettingItem item)
        {
            if (string.IsNullOrEmpty(sectionName))
            {
                throw new ArgumentException(Resources.Argument_Cannot_Be_Null_Or_Empty, nameof(sectionName));
            }

            if (item == null)
            {
                throw new ArgumentNullException(nameof(item));
            }

            var currentSettings = Priority.Last(f => f.Equals(settingsFile));
            if (settingsFile.IsMachineWide || (currentSettings?.IsMachineWide ?? false))
            {
                throw new InvalidOperationException(Resources.CannotUpdateMachineWide);
            }

            if (settingsFile.IsReadOnly || (currentSettings?.IsReadOnly ?? false))
            {
                throw new InvalidOperationException(Resources.CannotUpdateReadOnlyConfig);
            }

            if (currentSettings == null)
            {
                SettingsFiles.Add(settingsFile);
            }

            // If it is an update this will take care of it and modify the underlaying object, which is also referenced by _computedSections.
            settingsFile.AddOrUpdate(sectionName, item);

            // AddOrUpdate should have created this section, therefore this should always exist.
            settingsFile.TryGetSection(sectionName, out SettingSection? retrievedSettingFileSection);
            SettingSection settingFileSection = retrievedSettingFileSection!;

            // If it is an add we have to manually add it to the _computedSections.
            if (_computedSections.TryGetValue(sectionName, out var section))
            {
                if (!section.Items.Contains(item))
                {
                    var existingItem = settingFileSection.Items.First(i => i.Equals(item));
                    section.Add(existingItem);
                }
            }
            else
            {
                _computedSections.Add(sectionName,
                    new VirtualSettingSection(settingFileSection));
            }
        }

        public void Remove(string sectionName, SettingItem item)
        {
            if (string.IsNullOrEmpty(sectionName))
            {
                throw new ArgumentException(Resources.Argument_Cannot_Be_Null_Or_Empty, nameof(sectionName));
            }

            if (item == null)
            {
                throw new ArgumentNullException(nameof(item));
            }

            if (!_computedSections.TryGetValue(sectionName, out var section))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.SectionDoesNotExist, sectionName));
            }

            if (!section.Items.Contains(item))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.ItemDoesNotExist, sectionName));
            }

            section.Remove(item);

            if (section.IsEmpty())
            {
                _computedSections.Remove(sectionName);
            }
        }

        public event EventHandler? SettingsChanged = delegate { };

        public Settings(string root)
            : this(new List<SettingsFile> { new SettingsFile(root) }) { }

        public Settings(string root, string fileName)
            : this(new List<SettingsFile> { new SettingsFile(root, fileName) }) { }

        public Settings(string root, string fileName, bool isMachineWide)
            : this(new List<SettingsFile>() { new SettingsFile(root, fileName, isMachineWide, isReadOnly: false) })
        {
        }

        /// <summary>
        /// All the SettingsFiles represent by this settings object.
        /// The ordering is important, closest to furthest from the user.
        /// </summary>
        private IList<SettingsFile> SettingsFiles { get; }

        /// <summary>
        /// Create a settings object.
        /// The settings files need to be ordered from closest to furthest from the user.
        /// </summary>
        /// <param name="settingsFiles"></param>
        internal Settings(IList<SettingsFile> settingsFiles)
        {
            SettingsFiles = settingsFiles ?? throw new ArgumentNullException(nameof(settingsFiles));

            var computedSections = new Dictionary<string, VirtualSettingSection>();

            // They come in priority order, closest to furthest
            // reverse merge them, so the closest ones apply.
            for (int i = settingsFiles.Count - 1; i >= 0; i--)
            {
                settingsFiles[i].MergeSectionsInto(computedSections);
            }

            _computedSections = computedSections;
        }

        private SettingsFile? GetOutputSettingFileForSection(string sectionName)
        {
            // Search for the furthest from the user that can be written
            // to that is not clearing the ones before it on the hierarchy
            var writteableSettingsFiles = Priority.Where(f => !f.IsReadOnly);

            var clearedSections = writteableSettingsFiles.Select(f =>
            {
                if (f.TryGetSection(sectionName, out var section))
                {
                    return section;
                }
                return null;
            }).Where(s => s != null && s.Items.Contains(new ClearItem()));

            if (clearedSections.Any())
            {
                return clearedSections.First()!.Origin;
            }

            // if none have a clear tag, default to furthest from the user
            return writteableSettingsFiles.LastOrDefault();
        }

        /// <summary>
        /// Enumerates the sequence of <see cref="SettingsFile"/> instances
        /// ordered from closer to user to furthest
        /// </summary>
        internal IEnumerable<SettingsFile> Priority => SettingsFiles;

        public void SaveToDisk()
        {
            foreach (var settingsFile in Priority)
            {
                settingsFile.SaveToDisk();
            }

            SettingsChanged?.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        /// Load default settings based on a directory.
        /// This includes machine wide settings.
        /// </summary>
        public static ISettings LoadDefaultSettings(string? root)
        {
            return LoadSettings(
                root,
                configFileName: null,
                machineWideSettings: new XPlatMachineWideSetting(),
                loadUserWideSettings: true,
                useTestingGlobalPath: false);
        }

        /// <summary>
        /// Loads user settings from the NuGet configuration files. The method walks the directory
        /// tree in <paramref name="root" /> up to its root, and reads each NuGet.config file
        /// it finds in the directories. It then reads the user specific settings,
        /// which is file <paramref name="configFileName" />
        /// in <paramref name="root" /> if <paramref name="configFileName" /> is not null,
        /// If <paramref name="configFileName" /> is null, the user specific settings file is
        /// %AppData%\NuGet\NuGet.config.
        /// After that, the machine wide settings files are added.
        /// </summary>
        /// <remarks>
        /// For example, if <paramref name="root" /> is c:\dir1\dir2, <paramref name="configFileName" />
        /// is "userConfig.file", the files loaded are (in the order that they are loaded):
        /// c:\dir1\dir2\nuget.config
        /// c:\dir1\nuget.config
        /// c:\nuget.config
        /// c:\dir1\dir2\userConfig.file
        /// machine wide settings (e.g. c:\programdata\NuGet\Config\*.config)
        /// </remarks>
        /// <param name="root">
        /// The file system to walk to find configuration files.
        /// Can be null.
        /// </param>
        /// <param name="configFileName">The user specified configuration file.</param>
        /// <param name="machineWideSettings">
        /// The machine wide settings. If it's not null, the
        /// settings files in the machine wide settings are added after the user specific
        /// config file.
        /// </param>
        /// <returns>The settings object loaded.</returns>
        public static ISettings LoadDefaultSettings(
            string? root,
            string? configFileName,
            IMachineWideSettings? machineWideSettings)
        {
            return LoadDefaultSettings(root, configFileName, machineWideSettings, settingsLoadingContext: null);
        }

        /// <summary>
        /// Loads user settings from the NuGet configuration files. The method walks the directory
        /// tree in <paramref name="root" /> up to its root, and reads each NuGet.config file
        /// it finds in the directories. It then reads the user specific settings,
        /// which is file <paramref name="configFileName" />
        /// in <paramref name="root" /> if <paramref name="configFileName" /> is not null,
        /// If <paramref name="configFileName" /> is null, the user specific settings file is
        /// %AppData%\NuGet\NuGet.config.
        /// After that, the machine wide settings files are added.
        /// </summary>
        /// <remarks>
        /// For example, if <paramref name="root" /> is c:\dir1\dir2, <paramref name="configFileName" />
        /// is "userConfig.file", the files loaded are (in the order that they are loaded):
        /// c:\dir1\dir2\nuget.config
        /// c:\dir1\nuget.config
        /// c:\nuget.config
        /// c:\dir1\dir2\userConfig.file
        /// machine wide settings (e.g. c:\programdata\NuGet\Config\*.config)
        /// </remarks>
        /// <param name="root">
        /// The file system to walk to find configuration files.
        /// Can be null.
        /// </param>
        /// <param name="configFileName">The user specified configuration file.</param>
        /// <param name="machineWideSettings">
        /// The machine wide settings. If it's not null, the
        /// settings files in the machine wide settings are added after the user specific
        /// config file.
        /// </param>
        /// <param name="settingsLoadingContext">A <see cref="SettingsLoadingContext" /> object to use when loading the settings.</param>
        /// <returns>The settings object loaded.</returns>
        public static ISettings LoadDefaultSettings(
            string? root,
            string? configFileName,
            IMachineWideSettings? machineWideSettings,
            SettingsLoadingContext? settingsLoadingContext)
        {
            return LoadSettings(
                root,
                configFileName,
                machineWideSettings,
                loadUserWideSettings: true,
                useTestingGlobalPath: false,
                settingsLoadingContext);
        }

        /// <summary>
        /// Loads Specific NuGet.Config file. The method only loads specific config file 
        /// which is file <paramref name="configFileName"/>from <paramref name="root"/>.
        /// </summary>
        public static ISettings LoadSpecificSettings(string root, string configFileName)
        {
            if (string.IsNullOrEmpty(configFileName))
            {
                throw new ArgumentException(Resources.Argument_Cannot_Be_Null_Or_Empty, nameof(configFileName));
            }

            return LoadSettings(
                root,
                configFileName,
                machineWideSettings: null,
                loadUserWideSettings: true,
                useTestingGlobalPath: false);
        }

        public static ISettings LoadImmutableSettingsGivenConfigPaths(IList<string>? configFilePaths, SettingsLoadingContext settingsLoadingContext)
        {
            if (configFilePaths == null || configFilePaths.Count == 0)
            {
                return NullSettings.Instance;
            }
            var settings = new List<SettingsFile>();

            foreach (var configFilePath in configFilePaths)
            {
                settings.Add(settingsLoadingContext.GetOrCreateSettingsFile(configFilePath, isReadOnly: true));
            }

            return new ImmutableSettings(LoadSettingsGivenSettingsFiles(settings));
        }

        public static ISettings LoadSettingsGivenConfigPaths(IList<string> configFilePaths)
        {
            var settings = new List<SettingsFile>();
            if (configFilePaths == null || configFilePaths.Count == 0)
            {
                return NullSettings.Instance;
            }

            foreach (var configFile in configFilePaths)
            {
                var file = new FileInfo(configFile);
                settings.Add(new SettingsFile(file.DirectoryName!, file.Name));
            }

            return LoadSettingsGivenSettingsFiles(settings);
        }


        private static ISettings LoadSettingsGivenSettingsFiles(List<SettingsFile> settings)
        {
            return LoadSettingsForSpecificConfigs(
                            settings.First().DirectoryPath,
                            settings.First().FileName,
                            validSettingFiles: settings,
                            machineWideSettings: null,
                            loadUserWideSettings: false,
                            useTestingGlobalPath: false);
        }


        /// <summary>
        /// For internal use only
        /// </summary>
        internal static ISettings LoadSettings(
            string? root,
            string? configFileName,
            IMachineWideSettings? machineWideSettings,
            bool loadUserWideSettings,
            bool useTestingGlobalPath,
            SettingsLoadingContext? settingsLoadingContext = null)
        {
            // Walk up the tree to find a config file; also look in .nuget subdirectories
            // If a configFile is passed, don't walk up the tree. Only use that single config file.
            var validSettingFiles = new List<SettingsFile>();
            if (root != null && string.IsNullOrEmpty(configFileName))
            {
                validSettingFiles.AddRange(
                    GetSettingsFilesFullPath(root)
                        .Select(f =>
                        {
                            if (settingsLoadingContext == null)
                            {
                                return ReadSettings(root, f);
                            }
                            return settingsLoadingContext.GetOrCreateSettingsFile(f);
                        })
                        .OfType<SettingsFile>());
            }

            return LoadSettingsForSpecificConfigs(
                root,
                configFileName,
                validSettingFiles,
                machineWideSettings,
                loadUserWideSettings,
                useTestingGlobalPath,
                settingsLoadingContext);
        }

        /// <summary>
        /// For internal use only.
        /// Finds and loads all configuration files within <paramref name="root" />.
        /// Does not load configuration files outside of <paramref name="root" />.
        /// </summary>
        internal static ISettings LoadSettings(
            DirectoryInfo root,
            IMachineWideSettings? machineWideSettings,
            bool loadUserWideSettings,
            bool useTestingGlobalPath,
            SettingsLoadingContext? settingsLoadingContext = null)
        {
            var validSettingFiles = new List<SettingsFile>();
            var comparer = PathUtility.GetStringComparisonBasedOnOS();
            var settingsFiles = root.GetFileSystemInfos("*.*", SearchOption.AllDirectories)
                .OfType<FileInfo>()
                .OrderByDescending(file => file.FullName.Count(c => c == Path.DirectorySeparatorChar))
                .Where(file => OrderedSettingsFileNames.Any(fileName => fileName.Equals(file.Name, comparer)));

            validSettingFiles.AddRange(
                settingsFiles
                    .Select(file => ReadSettings(file.DirectoryName!, file.FullName, settingsLoadingContext: settingsLoadingContext))
                    .OfType<SettingsFile>());

            return LoadSettingsForSpecificConfigs(
                root.FullName,
                configFileName: null,
                validSettingFiles,
                machineWideSettings,
                loadUserWideSettings,
                useTestingGlobalPath,
                settingsLoadingContext);
        }

        private static ISettings LoadSettingsForSpecificConfigs(
            string? root,
            string? configFileName,
            List<SettingsFile> validSettingFiles,
            IMachineWideSettings? machineWideSettings,
            bool loadUserWideSettings,
            bool useTestingGlobalPath,
            SettingsLoadingContext? settingsLoadingContext = null)
        {
            if (loadUserWideSettings)
            {
                validSettingFiles.AddRange(LoadUserSpecificSettings(root, configFileName, useTestingGlobalPath, settingsLoadingContext));
            }

            if (machineWideSettings != null && machineWideSettings.Settings is Settings mwSettings && string.IsNullOrEmpty(configFileName))
            {
                // Priority gives you the settings file in the order you want to start reading them
                var files = mwSettings.Priority.Select(
                    s => ReadSettings(s.DirectoryPath, s.FileName, s.IsMachineWide, settingsLoadingContext: settingsLoadingContext))
                    .Where(s => s != null)
                    .Cast<SettingsFile>();

                validSettingFiles.AddRange(files);
            }

            if (validSettingFiles?.Any() != true)
            {
                // This means we've failed to load all config files and also failed to load or create the one in %AppData%
                // Work Item 1531: If the config file is malformed and the constructor throws, NuGet fails to load in VS.
                // Returning a null instance prevents us from silently failing and also from picking up the wrong config
                return NullSettings.Instance;
            }

            // Create a settings object with the linked list head. Typically, it's either the config file in %ProgramData%\NuGet\Config,
            // or the user wide config (%APPDATA%\NuGet\nuget.config) if there are no machine
            // wide config files. The head file is the one we want to read first, while the user wide config
            // is the one that we want to write to.
            // TODO: add UI to allow specifying which one to write to
            return new Settings(settingsFiles: validSettingFiles);
        }

        /// <summary>
        /// Load the user specific settings
        /// </summary>
        /// <param name="root"></param>
        /// <param name="configFileName"></param>
        /// <param name="useTestingGlobalPath"></param>
        /// <param name="settingsLoadingContext"></param>
        /// <returns></returns>
        internal static IEnumerable<SettingsFile> LoadUserSpecificSettings(
            string? root,
            string? configFileName,
            bool useTestingGlobalPath,
            SettingsLoadingContext? settingsLoadingContext = null)
        {
            // Path.Combine is performed with root so it should not be null
            // However, it is legal for it be empty in this method
            var rootDirectory = root ?? string.Empty;

            if (configFileName == null)
            {
                string userSettingsDir = GetUserSettingsDirectory(rootDirectory, useTestingGlobalPath);
                if (userSettingsDir == null)
                {
                    yield break;
                }

                var defaultSettingsFilePath = Path.Combine(userSettingsDir, DefaultSettingsFileName);

                // ReadSettings will try to create the default config file if it doesn't exist
                SettingsFile? userSpecificSettings = ReadSettings(rootDirectory, defaultSettingsFilePath, settingsLoadingContext: settingsLoadingContext);

                if (userSpecificSettings != null)
                {
                    yield return userSpecificSettings;
                }

                // For backwards compatibility, we first return default user specific the non-default configs and then the additional files from the nested `config` directory
                var additionalConfigurationPath = GetAdditionalUserWideConfigurationDirectory(userSettingsDir);
                foreach (var file in FileSystemUtility
                    .GetFilesRelativeToRoot(root: additionalConfigurationPath, filters: SupportedMachineWideConfigExtension, searchOption: SearchOption.TopDirectoryOnly)
                    .OrderBy(e => e, PathUtility.GetStringComparerBasedOnOS()))
                {
                    if (!PathUtility.GetStringComparerBasedOnOS().Equals(DefaultSettingsFileName, file))
                    {
                        var settings = ReadSettings(additionalConfigurationPath, file, isMachineWideSettings: false, isAdditionalUserWideConfig: true);
                        if (settings != null)
                        {
                            yield return settings;
                        }
                    }
                }
            }
            else
            {
                if (!FileSystemUtility.DoesFileExistIn(rootDirectory, configFileName))
                {
                    var message = string.Format(CultureInfo.CurrentCulture,
                        Resources.FileDoesNotExist,
                        Path.Combine(rootDirectory, configFileName));
                    throw new InvalidOperationException(message);
                }
                var settings = ReadSettings(rootDirectory, configFileName, settingsLoadingContext: settingsLoadingContext);
                if (settings != null)
                {
                    yield return settings;
                }
            }
        }

        private static string GetUserSettingsDirectory(string rootDirectory, bool useTestingGlobalPath)
        {
            return useTestingGlobalPath
                ? Path.Combine(rootDirectory, "TestingGlobalPath")
                : NuGetEnvironment.GetFolderPath(NuGetFolderPath.UserSettingsDirectory);
        }

        private static string GetAdditionalUserWideConfigurationDirectory(string userSettingsDirectory)
        {
            return Path.Combine(userSettingsDirectory, ConfigurationConstants.Config);
        }

        /// <summary>
        /// Loads the machine wide settings.
        /// </summary>
        /// <remarks>
        /// For example, if <paramref name="paths" /> is {"IDE", "Version", "SKU" }, then
        /// the files loaded are (in the order that they are loaded):
        /// %programdata%\NuGet\Config\IDE\Version\SKU\*.config
        /// %programdata%\NuGet\Config\IDE\Version\*.config
        /// %programdata%\NuGet\Config\IDE\*.config
        /// %programdata%\NuGet\Config\*.config
        /// </remarks>
        /// <param name="root">The file system in which the settings files are read.</param>
        /// <param name="paths">The additional paths under which to look for settings files.</param>
        /// <returns>The list of settings read.</returns>
        public static ISettings LoadMachineWideSettings(
            string root,
            params string[] paths)
        {
            if (string.IsNullOrEmpty(root))
            {
                throw new ArgumentException("root cannot be null or empty");
            }

            var settingFiles = new List<SettingsFile>();
            var combinedPath = Path.Combine(paths);

            while (true)
            {
                // load setting files in directory
                foreach (var file in FileSystemUtility.GetFilesRelativeToRoot(root, combinedPath, SupportedMachineWideConfigExtension, SearchOption.TopDirectoryOnly))
                {
                    var settings = ReadSettings(root, file, isMachineWideSettings: true);
                    if (settings != null)
                    {
                        settingFiles.Add(settings);
                    }
                }

                if (combinedPath.Length == 0)
                {
                    break;
                }

                var index = combinedPath.LastIndexOf(Path.DirectorySeparatorChar);
                if (index < 0)
                {
                    index = 0;
                }
                combinedPath = combinedPath.Substring(0, index);
            }

            if (settingFiles.Any())
            {
                return new Settings(settingFiles);
            }

            return NullSettings.Instance;
        }

        public static string ApplyEnvironmentTransform(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return value;
            }

            return Environment.ExpandEnvironmentVariables(value);
        }

        public static Tuple<string, string> GetFileNameAndItsRoot(string root, string settingsPath)
        {
            string fileName;
            string directory;

            if (Path.IsPathRooted(settingsPath))
            {
                fileName = Path.GetFileName(settingsPath);
                directory = Path.GetDirectoryName(settingsPath)!;
            }
            else if (!FileSystemUtility.IsPathAFile(settingsPath))
            {
                var fullPath = Path.Combine(root ?? string.Empty, settingsPath);
                fileName = Path.GetFileName(fullPath);
                directory = Path.GetDirectoryName(fullPath)!;
            }
            else
            {
                fileName = settingsPath;
                directory = root;
            }

            return new Tuple<string, string>(fileName, directory);
        }

        /// <summary>
        /// Get a list of all the paths of the settings files used as part of this settings object
        /// </summary>
        public IList<string> GetConfigFilePaths()
        {
            return Priority.Select(config => config.ConfigFilePath).ToList();
        }

        /// <summary>
        /// Get a list of all the roots of the settings files used as part of this settings object
        /// </summary>
        public IList<string> GetConfigRoots()
        {
            return Priority.Select(config => config.DirectoryPath).Distinct().ToList();
        }

        internal static string ResolvePathFromOrigin(string originDirectoryPath, string originFilePath, string path)
        {
            if (Uri.TryCreate(path, UriKind.Relative, out var _) &&
                !string.IsNullOrEmpty(originDirectoryPath) &&
                !string.IsNullOrEmpty(originFilePath))
            {
                return ResolveRelativePath(originDirectoryPath, originFilePath, path);
            }

            return path;
        }

        private static string ResolveRelativePath(string originDirectoryPath, string originFilePath, string path)
        {
            if (string.IsNullOrEmpty(originDirectoryPath) || string.IsNullOrEmpty(originFilePath))
            {
#pragma warning disable CS8603 // Possible null reference return.
                // This code path doesn't seem possible, but if I'm wrong, then deleting this block will change the behavior of the code.
                return null;
#pragma warning restore CS8603 // Possible null reference return.
            }

            if (string.IsNullOrEmpty(path))
            {
                return path;
            }

            var rawPath = Path.Combine(originDirectoryPath, ResolvePath(Path.GetDirectoryName(originFilePath)!, path));
            var normalizedPath = new DirectoryInfo(rawPath).FullName;
            return normalizedPath;
        }

        private static string ResolvePath(string configDirectory, string value)
        {
            // Three cases for when Path.IsRooted(value) is true:
            // 1- C:\folder\file
            // 2- \\share\folder\file
            // 3- \folder\file
            // In the first two cases, we want to honor the fully qualified path
            // In the last case, we want to return X:\folder\file with X: drive where config file is located.
            // However, Path.Combine(path1, path2) always returns path2 when Path.IsRooted(path2) == true (which is current case)
            string root;

            try
            {
                root = Path.GetPathRoot(value)!;
            }
            catch (ArgumentException ex)
            {
                throw new NuGetConfigurationException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.ShowError_ConfigHasInvalidPackageSource, NuGetLogCode.NU1006, value, ex.Message),
                    ex);
            }

            // this corresponds to 3rd case
            if (root != null
                && root.Length == 1
                && (root[0] == Path.DirectorySeparatorChar || value[0] == Path.AltDirectorySeparatorChar))
            {
                return Path.Combine(Path.GetPathRoot(configDirectory)!, value.Substring(1));
            }
            return Path.Combine(configDirectory, value);
        }

        private static SettingsFile? ReadSettings(string settingsRoot, string settingsPath, bool isMachineWideSettings = false, bool isAdditionalUserWideConfig = false, SettingsLoadingContext? settingsLoadingContext = null)
        {
            try
            {
                if (settingsLoadingContext != null)
                {
                    if (!Path.IsPathRooted(settingsPath) && !string.IsNullOrWhiteSpace(settingsRoot))
                    {
                        settingsPath = Path.Combine(settingsRoot, settingsPath);
                    }

                    return settingsLoadingContext.GetOrCreateSettingsFile(settingsPath, isMachineWideSettings, isAdditionalUserWideConfig);
                }

                var tuple = GetFileNameAndItsRoot(settingsRoot, settingsPath);
                var filename = tuple.Item1;
                var root = tuple.Item2;
                return new SettingsFile(root, filename, isMachineWideSettings, isAdditionalUserWideConfig);
            }
            catch (XmlException)
            {
                return null;
            }
        }

        /// <remarks>
        /// Order is most significant (e.g. applied last) to least significant (applied first)
        /// ex:
        /// c:\someLocation\nuget.config
        /// c:\nuget.config
        /// </remarks>
        private static IEnumerable<string> GetSettingsFilesFullPath(string root)
        {
            // for dirs obtained by walking up the tree, only consider setting files that already exist.
            // otherwise we'd end up creating them.
            foreach (var dir in GetSettingsFilePaths(root))
            {
                var fileName = GetSettingsFileNameFromDir(dir);
                if (fileName != null)
                {
                    yield return fileName;
                }
            }

            yield break;
        }

        /// <summary>
        /// Checks for each possible casing of nuget.config in the directory. The first match is
        /// returned. If there are no nuget.config files null is returned.
        /// </summary>
        /// <remarks>For windows <see cref="OrderedSettingsFileNames"/> contains a single casing since
        /// the file system is case insensitive.</remarks>
        private static string? GetSettingsFileNameFromDir(string directory)
        {
            foreach (var nugetConfigCasing in OrderedSettingsFileNames)
            {
                var file = Path.Combine(directory, nugetConfigCasing);
                if (File.Exists(file))
                {
                    return file;
                }
            }

            return null;
        }

        private static IEnumerable<string> GetSettingsFilePaths(string root)
        {
            string? current = root;
            while (current != null)
            {
                yield return current;
                current = Path.GetDirectoryName(current);
            }

            yield break;
        }
    }
}