File: UriUtility.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Common\NuGet.Common.csproj (NuGet.Common)
// 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.IO;

namespace NuGet.Common
{
    public static class UriUtility
    {
        private const string FilePrefix = "file://";

        private static bool IsHttpUrl(Uri uri)
        {
            return uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps;
        }

        /// <summary>
        /// Same as "new Uri" except that it can handle UNIX style paths that start with '/'
        /// </summary>
        public static Uri CreateSourceUri(string source, UriKind kind = UriKind.Absolute)
        {
            source = FixSourceUri(source);
            return new Uri(source, kind);
        }

        /// <summary>
        /// Same as "Uri.TryCreate" except that it can handle UNIX style paths that start with '/'
        /// </summary>
        public static Uri? TryCreateSourceUri(string source, UriKind kind)
        {
            source = FixSourceUri(source);

            Uri? uri;
            return Uri.TryCreate(source, kind, out uri) ? uri : null;
        }

        private static string FixSourceUri(string source)
        {
            // UNIX absolute paths need to start with file://
            if (Path.DirectorySeparatorChar == '/' && !string.IsNullOrEmpty(source) && source[0] == '/')
            {
                source = FilePrefix + source;
            }

            return source;
        }

        /// <summary>
        /// Provides Uri encoding for V2 servers in the same way that NuGet.Core.dll encoded urls.
        /// </summary>
        public static string UrlEncodeOdataParameter(string value)
        {
            if (!string.IsNullOrEmpty(value))
            {
                // OData requires that a single quote MUST be escaped as 2 single quotes.
                // In .NET 4.5, Uri.EscapeDataString() escapes single quote as %27. Thus we must replace %27 with 2 single quotes.
                // In .NET 4.0, Uri.EscapeDataString() doesn't escape single quote. Thus we must replace it with 2 single quotes.
                return Uri.EscapeDataString(value).Replace("'", "''").Replace("%27", "''");
            }

            return value;
        }

        /// <summary>
        /// Convert a file:// URI to a local path.
        /// </summary>
        /// <returns>If the input can be parsed this will return Uri.LocalPath, if the input 
        /// is not a URI or fails to parse the original string will be returned.</returns>
        /// <param name="localOrUriPath">Possible file:// URI path or local path.</param>
        public static string GetLocalPath(string localOrUriPath)
        {
            // check if this starts with file://
            if (!string.IsNullOrEmpty(localOrUriPath)
                && localOrUriPath.StartsWith(FilePrefix, StringComparison.OrdinalIgnoreCase))
            {
                // convert to a uri and get the local path
                Uri? uri;
                if (Uri.TryCreate(localOrUriPath, UriKind.RelativeOrAbsolute, out uri))
                {
                    return uri.LocalPath;
                }
            }

            // Return the same path
            return localOrUriPath;
        }

        /// <summary>
        /// Calls GetAbsolutePath with the directory of <paramref name="sourceFile"/>.
        /// </summary>
        public static string GetAbsolutePathFromFile(string? sourceFile, string path)
        {
            if (string.IsNullOrEmpty(sourceFile))
            {
                return path;
            }

            return GetAbsolutePath(Path.GetDirectoryName(sourceFile!), path)!;
        }

        /// <summary>
        /// Convert a relative local folder path to an absolute path.
        /// For http sources and UNC shares this will return
        /// the same path.
        /// </summary>
        /// <param name="rootDirectory">Directory to make the source relative to.</param>
        /// <param name="path">Source path.</param>
        /// <returns>The absolute source path or the original source. Noops for non-file paths.</returns>
        public static string? GetAbsolutePath(string? rootDirectory, string? path)
        {
            // return invalid data as-is.
            if (string.IsNullOrEmpty(rootDirectory) || string.IsNullOrEmpty(path))
            {
                return path;
            }

            // Convert file:// to a path
            var local = GetLocalPath(path!);

            // Check if the result is relative, in which case combine it.
            var relativeUri = TryCreateSourceUri(local, UriKind.Relative);

            if (relativeUri != null)
            {
                // Combine with the root dir
                return Path.GetFullPath(Path.Combine(rootDirectory, local));
            }

            var absoluteUri = TryCreateSourceUri(local, UriKind.Absolute);

            if (absoluteUri?.IsFile == true)
            {
                return Path.GetFullPath(local);
            }

            // Absolute path or non-http url.
            return local;
        }

        /// <summary>
        /// Determines if a package source url points to nuget.org
        /// </summary>
        /// <param name="source">Package source url</param>
        /// <returns>True if the source is HTTP and has a *.nuget.org or nuget.org host otherwise false</returns>
        public static bool IsNuGetOrg(string? source)
        {
            if (string.IsNullOrWhiteSpace(source))
            {
                return false;
            }

            var uri = TryCreateSourceUri(source!, UriKind.Absolute);

            if (uri == null || !IsHttpUrl(uri))
            {
                return false;
            }

            if (StringComparer.OrdinalIgnoreCase.Equals(uri.Host, "nuget.org")
                || uri.Host.EndsWith(".nuget.org", StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            return false;
        }
    }
}