File: Devcontainers\Codespaces\CodespacesUrlRewriter.cs
Web Access
Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Options;
 
namespace Aspire.Hosting.Devcontainers.Codespaces;
 
internal sealed class CodespacesUrlRewriter(IOptions<CodespacesOptions> options)
{
    public bool TryRewriteResourceUrl(string url, [NotNullWhen(true)] out string? rewrittenUrl)
    {
        rewrittenUrl = null;
        if (!options.Value.IsCodespace ||
            !Uri.TryCreate(url, UriKind.Absolute, out var uri) ||
            uri.Scheme is not ("http" or "https") ||
            !string.Equals(uri.Host, "localhost", StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }
 
        rewrittenUrl = RewriteUrl(uri);
        return true;
    }
 
    public string RewriteUrl(string url)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(url);
 
        if (!options.Value.IsCodespace)
        {
            return url;
        }
 
        return RewriteUrl(new Uri(url));
    }
 
    public string RewriteUrl(Uri uri)
    {
        if (!options.Value.IsCodespace)
        {
            return uri.ToString();
        }
 
        // Forwarded addresses use <codespace>-<local-port>.<forwarding-domain>;
        // the local port is encoded in the hostname, not in the public authority.
        // The public URL uses HTTPS independently of the protocol forwarded to the service:
        // https://docs.github.com/en/codespaces/developing-in-a-codespace/forwarding-ports-in-your-codespace#using-command-line-tools-and-rest-clients-to-access-ports
        var codespacesUrl = $"https://{options.Value.CodespaceName}-{uri.Port}.{options.Value.PortForwardingDomain}{uri.AbsolutePath}{uri.Query}{uri.Fragment}";
        return codespacesUrl;
    }
}