Skip to content

[API Proposal]: UriCreationOptions #59099

@MihaZupan

Description

@MihaZupan

Background and motivation

Uri validates the input and (if needed) converts it into a normalized (canonical) representation.
For example, it will:

  • Escape reserved characters (spaces turn into %20)
  • Unescape unreserved characters (%41 turns into A)
  • Compress dot segments (/foo/./bar/../ turns into /foo/)

It does this based on the recommendations of the Uri RFC.

Some services, however, rely on specific behavior that may be impossible to replicate using .NET's Uri (#52628, #58057).
For example, it is currently not possible to send /foo?A=%42 with HttpClient, as Uri will convert it to /foo?A=B.

I propose adding a UriCreationOptions type and a ctor/TryCreate overload to consume it, serving as the extension point for future Uri customizability.

Uri has support for implicitly converting a file path into a file:// uri.
/foo is a valid Unix absolute file path and support for Unix paths is limited to non-Windows platforms.
This can create confusion in cases like Uri.TryCreate("/foo", UriKind.Absolute) where the behavior depends on the platform.
Given an extension point like UriCreationOptions is available, I propose adding an AllowImplicitFilePaths flag to it.

API Proposal

namespace System
{
    // New type
    public readonly struct UriCreationOptions
    {
        public bool AllowImplicitFilePaths { get; init; }
        public bool DangerousUseRawPathAndQuery { get; init; }

        public UriKind UriKind { get; }

        public UriCreationOptions(UriKind uriKind);
    }

    public partial class Uri
    {
        // Existing
        public Uri(string uriString);
        public Uri(string uriString, UriKind uriKind);
        // New
        public Uri(string uriString, UriCreationOptions creationOptions);
        
        // Existing
        public static bool TryCreate(string? uriString, UriKind uriKind, out Uri? result);
        // New
        public static bool TryCreate(string? uriString, UriCreationOptions creationOptions, out Uri? result);
    }
}

Alternative names for DangerousUseRawPathAndQuery:

  • DangerousUseRawTarget
  • DangerousDisableCanonicalization

Behavior of DangerousUseRawPathAndQuery is such that we try not to be opinionated as much as possible:

  • Validation of the Path/Query is skipped, we assume they already are in the “perfect format”
  • We will not escape reserved characters (e.g. spaces won't be turned into %20)
  • We will not unescape unreserved characters (e.g. %41 won't be turned into A)
  • We will not compress dot segments (/../ or /./ will be kept)
  • Fragment is always empty – # and anything following it is attributed to the Path or Query
    • Not doing so would disallow sending the # literal as part of PathAndQuery
  • Properties like Path, Query, PathAndQuery will return the value as-is
  • We will not ensure Path/PathAndQuery start with /, that is left up to the user
  • Uris with DangerousUseRawPathAndQuery may only be equal to Uris with the same flag
  • The GetComponents API will ignore the UriFormat argument (values UriEscaped, Unescaped, SafeUnescaped) for Path & Query components

API Usage

string uriString = "/foo";

Uri.TryCreate(uriString, new UriCreationOptions { AllowImplicitFilePaths = false }, out var uri); // False
var options = new UriCreationOptions { DangerousUseRawPathAndQuery = true };
var uri = new Uri("http://foo/%41", options);

// Server receives /%41 instead of /A

Alternative API

  • Instead of a readonly struct, make UriCreationOptions an enum

    • Prevents us from easily exposing more complex options (e.g. ints, callbacks) in the future
    • Forces us into inverting names in some cases (e.g. AllowImplicitFilePaths would have to be DontAllowImplicitFilePaths) to match correctly defaults
    • Defers validation – with UriCreationOptions we can ensure it's always in a valid state (e.g. valid UriKind)
  • Instead of a DangerousUseRawPathAndQuery flag, expose granular options controlling specific transformations

    • Example: UnescapeUnreservedCharacters, CompressDotSegments
    • Any new scenarios that can't be addressed with existing options are blocked on the decision of the networking team, API review, implementation, and release of a major .NET version
    • Complicates the code base & testing - some options may conflict

Risks

Setting DangerousUseRawPathAndQuery means no validation/transformation of the input will take place past the authority.
If the input is not already in the correct format, HTTP requests using such a Uri may be malformed.

For example:

  • Reserved characters will not be escaped - space characters will not be changed to %20

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions