// 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.Text; namespace Microsoft.AspNetCore.Http.Extensions { /// /// A helper class for constructing encoded Uris for use in headers and other Uris. /// public static class UriHelper { private const string ForwardSlash = "/"; private const string Pound = "#"; private const string QuestionMark = "?"; private const string SchemeDelimiter = "://"; /// /// Combines the given URI components into a string that is properly encoded for use in HTTP headers. /// /// The first portion of the request path associated with application root. /// The portion of the request path that identifies the requested resource. /// The query, if any. /// The fragment, if any. /// public static string BuildRelative( PathString pathBase = new PathString(), PathString path = new PathString(), QueryString query = new QueryString(), FragmentString fragment = new FragmentString()) { string combinePath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/"; return combinePath + query.ToString() + fragment.ToString(); } /// /// Combines the given URI components into a string that is properly encoded for use in HTTP headers. /// Note that unicode in the HostString will be encoded as punycode. /// /// http, https, etc. /// The host portion of the uri normally included in the Host header. This may include the port. /// The first portion of the request path associated with application root. /// The portion of the request path that identifies the requested resource. /// The query, if any. /// The fragment, if any. /// public static string BuildAbsolute( string scheme, HostString host, PathString pathBase = new PathString(), PathString path = new PathString(), QueryString query = new QueryString(), FragmentString fragment = new FragmentString()) { var combinedPath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/"; var encodedHost = host.ToString(); var encodedQuery = query.ToString(); var encodedFragment = fragment.ToString(); // PERF: Calculate string length to allocate correct buffer size for StringBuilder. var length = scheme.Length + SchemeDelimiter.Length + encodedHost.Length + combinedPath.Length + encodedQuery.Length + encodedFragment.Length; return new StringBuilder(length) .Append(scheme) .Append(SchemeDelimiter) .Append(encodedHost) .Append(combinedPath) .Append(encodedQuery) .Append(encodedFragment) .ToString(); } /// /// Seperates the given absolute URI string into components. Assumes no PathBase. /// /// A string representation of the uri. /// http, https, etc. /// The host portion of the uri normally included in the Host header. This may include the port. /// The portion of the request path that identifies the requested resource. /// The query, if any. /// The fragment, if any. public static void FromAbsolute( string uri, out string scheme, out HostString host, out PathString path, out QueryString query, out FragmentString fragment) { if (uri == null) { throw new ArgumentNullException(nameof(uri)); } // Satisfy the out parameters path = new PathString(); query = new QueryString(); fragment = new FragmentString(); var startIndex = uri.IndexOf(SchemeDelimiter); if (startIndex < 0) { throw new FormatException("No scheme delimiter in uri."); } scheme = uri.Substring(0, startIndex); // PERF: Calculate the end of the scheme for next IndexOf startIndex += SchemeDelimiter.Length; var searchIndex = -1; var limit = uri.Length; if ((searchIndex = uri.IndexOf(Pound, startIndex)) >= 0 && searchIndex < limit) { fragment = FragmentString.FromUriComponent(uri.Substring(searchIndex)); limit = searchIndex; } if ((searchIndex = uri.IndexOf(QuestionMark, startIndex)) >= 0 && searchIndex < limit) { query = QueryString.FromUriComponent(uri.Substring(searchIndex, limit - searchIndex)); limit = searchIndex; } if ((searchIndex = uri.IndexOf(ForwardSlash, startIndex)) >= 0 && searchIndex < limit) { path = PathString.FromUriComponent(uri.Substring(searchIndex, limit - searchIndex)); limit = searchIndex; } host = HostString.FromUriComponent(uri.Substring(startIndex, limit - startIndex)); } /// /// Generates a string from the given absolute or relative Uri that is appropriately encoded for use in /// HTTP headers. Note that a unicode host name will be encoded as punycode. /// /// The Uri to encode. /// public static string Encode(Uri uri) { if (uri.IsAbsoluteUri) { return BuildAbsolute( scheme: uri.Scheme, host: HostString.FromUriComponent(uri), pathBase: PathString.FromUriComponent(uri), query: QueryString.FromUriComponent(uri), fragment: FragmentString.FromUriComponent(uri)); } else { return uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); } } /// /// Returns the combined components of the request URL in a fully escaped form suitable for use in HTTP headers /// and other HTTP operations. /// /// The request to assemble the uri pieces from. /// public static string GetEncodedUrl(this HttpRequest request) { return BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path, request.QueryString); } /// /// Returns the relative url /// /// The request to assemble the uri pieces from. /// public static string GetEncodedPathAndQuery(this HttpRequest request) { return BuildRelative(request.PathBase, request.Path, request.QueryString); } /// /// Returns the combined components of the request URL in a fully un-escaped form (except for the QueryString) /// suitable only for display. This format should not be used in HTTP headers or other HTTP operations. /// /// The request to assemble the uri pieces from. /// public static string GetDisplayUrl(this HttpRequest request) { var host = request.Host.Value; var pathBase = request.PathBase.Value; var path = request.Path.Value; var queryString = request.QueryString.Value; // PERF: Calculate string length to allocate correct buffer size for StringBuilder. var length = request.Scheme.Length + SchemeDelimiter.Length + host.Length + pathBase.Length + path.Length + queryString.Length; return new StringBuilder(length) .Append(request.Scheme) .Append(SchemeDelimiter) .Append(host) .Append(pathBase) .Append(path) .Append(queryString) .ToString(); } } }