From 6609e87944ffa5479b67b408636ab9367541667c Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Wed, 15 Sep 2021 18:08:35 +0200 Subject: [PATCH 1/2] Add UriCreationOptions Includes DangerousDisablePathAndQueryCanonicalization --- .../src/Resources/Strings.resx | 3 + .../src/System.Private.Uri.csproj | 1 + .../System.Private.Uri/src/System/Uri.cs | 63 +++- .../src/System/UriCreationOptions.cs | 10 + .../System.Private.Uri/src/System/UriExt.cs | 33 +- .../src/System/UriScheme.cs | 3 + ...System.Private.Uri.Functional.Tests.csproj | 1 + .../FunctionalTests/UriCreationOptionsTest.cs | 294 ++++++++++++++++++ .../System.Runtime/ref/System.Runtime.cs | 6 + 9 files changed, 408 insertions(+), 6 deletions(-) create mode 100644 src/libraries/System.Private.Uri/src/System/UriCreationOptions.cs create mode 100644 src/libraries/System.Private.Uri/tests/FunctionalTests/UriCreationOptionsTest.cs diff --git a/src/libraries/System.Private.Uri/src/Resources/Strings.resx b/src/libraries/System.Private.Uri/src/Resources/Strings.resx index 92a87cd44ccaa1..9975314e3d97c3 100644 --- a/src/libraries/System.Private.Uri/src/Resources/Strings.resx +++ b/src/libraries/System.Private.Uri/src/Resources/Strings.resx @@ -198,4 +198,7 @@ UriParser's base InitializeAndValidate may only be called once on a single Uri instance and only from an override of InitializeAndValidate. + + GetComponents() may not be used for Path/Query on a Uri instance created with UriCreationOptions.DangerousDisablePathAndQueryCanonicalization. + \ No newline at end of file diff --git a/src/libraries/System.Private.Uri/src/System.Private.Uri.csproj b/src/libraries/System.Private.Uri/src/System.Private.Uri.csproj index 4b63c5b3cb3061..ce5a97d87051bf 100644 --- a/src/libraries/System.Private.Uri/src/System.Private.Uri.csproj +++ b/src/libraries/System.Private.Uri/src/System.Private.Uri.csproj @@ -24,6 +24,7 @@ + diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index ad6efdba8540d1..806e7014eb604e 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -121,6 +121,8 @@ internal enum Flags : ulong IriCanonical = 0x78000000000, UnixPath = 0x100000000000, + DisablePathAndQueryCanonicalization = 0x200000000000, + /// /// Used to ensure that InitializeAndValidate is only called once per Uri instance and only from an override of InitializeAndValidate /// @@ -267,6 +269,8 @@ internal static bool IriParsingStatic(UriParser? syntax) return syntax is null || syntax.InFact(UriSyntaxFlags.AllowIriParsing); } + internal bool DisablePathAndQueryCanonicalization => (_flags & Flags.DisablePathAndQueryCanonicalization) != 0; + internal bool UserDrivenParsing { get @@ -410,6 +414,15 @@ public Uri(string uriString, UriKind uriKind) DebugSetLeftCtor(); } + public Uri(string uriString, in UriCreationOptions creationOptions) + { + if (uriString is null) + throw new ArgumentNullException(nameof(uriString)); + + CreateThis(uriString, false, UriKind.Absolute, in creationOptions); + DebugSetLeftCtor(); + } + // // Uri(Uri, string) // @@ -1639,6 +1652,9 @@ public override bool Equals([NotNullWhen(true)] object? comparand) // canonicalize the comparand, making comparison possible if (obj is null) { + if (DisablePathAndQueryCanonicalization) + return false; + if (!(comparand is string s)) return false; @@ -1649,6 +1665,9 @@ public override bool Equals([NotNullWhen(true)] object? comparand) return false; } + if (DisablePathAndQueryCanonicalization != obj.DisablePathAndQueryCanonicalization) + return false; + if (ReferenceEquals(OriginalString, obj.OriginalString)) { return true; @@ -2553,7 +2572,7 @@ private unsafe void GetHostViaCustomSyntax() // internal string GetParts(UriComponents uriParts, UriFormat formatAs) { - return GetComponents(uriParts, formatAs); + return InternalGetComponents(uriParts, formatAs); } private string GetEscapedParts(UriComponents uriParts) @@ -3158,9 +3177,6 @@ private unsafe void ParseRemaining() idx = _info.Offset.Path; origIdx = _info.Offset.Path; - //Some uris do not have a query - // When '?' is passed as delimiter, then it's special case - // so both '?' and '#' will work as delimiters if (buildIriStringFromPath) { DebugAssertInCtor(); @@ -3180,6 +3196,45 @@ private unsafe void ParseRemaining() _info.Offset.Path = (ushort)_string.Length; idx = _info.Offset.Path; + } + + // If the user explicitly disabled canonicalization, only figure out the offsets + if (DisablePathAndQueryCanonicalization) + { + if (buildIriStringFromPath) + { + DebugAssertInCtor(); + _string += _originalUnicodeString.Substring(origIdx); + } + + string str = _string; + + if (IsImplicitFile || (syntaxFlags & UriSyntaxFlags.MayHaveQuery) == 0) + { + idx = str.Length; + } + else + { + idx = str.IndexOf('?'); + if (idx == -1) + { + idx = str.Length; + } + } + + _info.Offset.Query = (ushort)idx; + _info.Offset.Fragment = (ushort)str.Length; // There is no fragment in UseRawTarget mode + _info.Offset.End = (ushort)str.Length; + + goto Done; + } + + //Some uris do not have a query + // When '?' is passed as delimiter, then it's special case + // so both '?' and '#' will work as delimiters + if (buildIriStringFromPath) + { + DebugAssertInCtor(); int offset = origIdx; if (IsImplicitFile || ((syntaxFlags & (UriSyntaxFlags.MayHaveQuery | UriSyntaxFlags.MayHaveFragment)) == 0)) diff --git a/src/libraries/System.Private.Uri/src/System/UriCreationOptions.cs b/src/libraries/System.Private.Uri/src/System/UriCreationOptions.cs new file mode 100644 index 00000000000000..b8ec6f874eab27 --- /dev/null +++ b/src/libraries/System.Private.Uri/src/System/UriCreationOptions.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System +{ + public struct UriCreationOptions + { + public bool DangerousDisablePathAndQueryCanonicalization { readonly get; set; } + } +} diff --git a/src/libraries/System.Private.Uri/src/System/UriExt.cs b/src/libraries/System.Private.Uri/src/System/UriExt.cs index a644dfa566699a..fee23e98ed9ceb 100644 --- a/src/libraries/System.Private.Uri/src/System/UriExt.cs +++ b/src/libraries/System.Private.Uri/src/System/UriExt.cs @@ -13,7 +13,7 @@ public partial class Uri // // All public ctors go through here // - private void CreateThis(string? uri, bool dontEscape, UriKind uriKind) + private void CreateThis(string? uri, bool dontEscape, UriKind uriKind, in UriCreationOptions creationOptions = default) { DebugAssertInCtor(); @@ -31,6 +31,9 @@ private void CreateThis(string? uri, bool dontEscape, UriKind uriKind) if (dontEscape) _flags |= Flags.UserEscaped; + if (creationOptions.DangerousDisablePathAndQueryCanonicalization) + _flags |= Flags.DisablePathAndQueryCanonicalization; + ParsingError err = ParseScheme(_string, ref _flags, ref _syntax!); InitializeUri(err, uriKind, out UriFormatException? e); @@ -259,6 +262,19 @@ public static bool TryCreate([NotNullWhen(true)] string? uriString, UriKind uriK return e is null && result != null; } + public static bool TryCreate([NotNullWhen(true)] string? uriString, in UriCreationOptions creationOptions, [NotNullWhen(true)] out Uri? result) + { + if (uriString is null) + { + result = null; + return false; + } + UriFormatException? e = null; + result = CreateHelper(uriString, false, UriKind.Absolute, ref e, in creationOptions); + result?.DebugSetLeftCtor(); + return e is null && result != null; + } + public static bool TryCreate(Uri? baseUri, string? relativeUri, [NotNullWhen(true)] out Uri? result) { if (TryCreate(relativeUri, UriKind.RelativeOrAbsolute, out Uri? relativeLink)) @@ -309,6 +325,16 @@ public static bool TryCreate(Uri? baseUri, Uri? relativeUri, [NotNullWhen(true)] } public string GetComponents(UriComponents components, UriFormat format) + { + if (DisablePathAndQueryCanonicalization && (components & (UriComponents.Path | UriComponents.Query)) != 0) + { + throw new InvalidOperationException(SR.net_uri_GetComponentsCalledWhenCanonicalizationDisabled); + } + + return InternalGetComponents(components, format); + } + + private string InternalGetComponents(UriComponents components, UriFormat format) { if (((components & UriComponents.SerializationInfoString) != 0) && components != UriComponents.SerializationInfoString) throw new ArgumentOutOfRangeException(nameof(components), components, SR.net_uri_NotJustSerialization); @@ -590,7 +616,7 @@ private Uri(Flags flags, UriParser? uriParser, string uri) // // a Uri.TryCreate() method goes through here. // - internal static Uri? CreateHelper(string uriString, bool dontEscape, UriKind uriKind, ref UriFormatException? e) + internal static Uri? CreateHelper(string uriString, bool dontEscape, UriKind uriKind, ref UriFormatException? e, in UriCreationOptions creationOptions = default) { // if (!Enum.IsDefined(typeof(UriKind), uriKind)) -- We currently believe that Enum.IsDefined() is too slow // to be used here. @@ -606,6 +632,9 @@ private Uri(Flags flags, UriParser? uriParser, string uri) if (dontEscape) flags |= Flags.UserEscaped; + if (creationOptions.DangerousDisablePathAndQueryCanonicalization) + flags |= Flags.DisablePathAndQueryCanonicalization; + // We won't use User factory for these errors if (err != ParsingError.None) { diff --git a/src/libraries/System.Private.Uri/src/System/UriScheme.cs b/src/libraries/System.Private.Uri/src/System/UriScheme.cs index d9d96ab59ea3cb..7c39a6c44a336e 100644 --- a/src/libraries/System.Private.Uri/src/System/UriScheme.cs +++ b/src/libraries/System.Private.Uri/src/System/UriScheme.cs @@ -146,6 +146,9 @@ protected virtual string GetComponents(Uri uri, UriComponents components, UriFor if (!uri.IsAbsoluteUri) throw new InvalidOperationException(SR.net_uri_NotAbsolute); + if (uri.DisablePathAndQueryCanonicalization && (components & (UriComponents.Path | UriComponents.Query)) != 0) + throw new InvalidOperationException(SR.net_uri_GetComponentsCalledWhenCanonicalizationDisabled); + return uri.GetComponentsHelper(components, format); } diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj b/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj index 8f07bb90af1ffe..21bb5200471aa1 100644 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj @@ -17,6 +17,7 @@ + diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/UriCreationOptionsTest.cs b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriCreationOptionsTest.cs new file mode 100644 index 00000000000000..3f7438738f0bf9 --- /dev/null +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/UriCreationOptionsTest.cs @@ -0,0 +1,294 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.PrivateUri.Tests +{ + public class UriCreationOptionsTest + { + [Fact] + public void UriCreationOptions_HasReasonableDefaults() + { + UriCreationOptions options = default; + + Assert.False(options.DangerousDisablePathAndQueryCanonicalization); + } + + [Fact] + public void UriCreationOptions_StoresCorrectValues() + { + var options = new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = true }; + Assert.True(options.DangerousDisablePathAndQueryCanonicalization); + + options = new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = false }; + Assert.False(options.DangerousDisablePathAndQueryCanonicalization); + } + + public static IEnumerable DisableCanonicalization_TestData() + { + var schemes = new string[] { "http", "hTTp", " http", "https" }; + var hosts = new string[] { "foo", "f\u00F6\u00F6.com" }; + var ports = new string[] { ":80", ":443", ":0123", ":", "" }; + + var pathAndQueries = new string[] + { + "", + " ", + "a b", + "a%20b", + "?a b", + "?a%20b", + "foo/./", + "foo/../", + "//\\//", + "%41", + "A?%41=%42", + "?%41=%42", + "? ", + }; + + var fragments = new string[] { "", "#", "#/foo ? %20%41/..//\\a" }; + var unicodeInPathModes = new int[] { 0, 1, 2, 3 }; + var pathDelimiters = new string[] { "", "/" }; + + // Get various combinations of paths with unicode characters and delimiters + string[] rawTargets = pathAndQueries + .SelectMany(pq => fragments.Select(fragment => pq + fragment)) + .SelectMany(pqf => unicodeInPathModes.Select(unicodeMode => unicodeMode switch + { + 0 => pqf, + 1 => "\u00F6" + pqf, + 2 => pqf + "\u00F6", + _ => pqf.Insert(pqf.Length / 2, "\u00F6") + })) + .ToHashSet() + .SelectMany(pqf => pathDelimiters.Select(delimiter => delimiter + pqf)) + .Where(target => target.StartsWith('/') || target.StartsWith('?')) // Can't see where the authority ends and the path starts otherwise + .ToArray(); + + foreach (string scheme in schemes) + { + foreach (string host in hosts) + { + foreach (string port in ports) + { + foreach (string rawTarget in rawTargets) + { + string uriString = $"{scheme}://{host}{port}{rawTarget}"; + + int expectedPort = port.Length > 1 ? int.Parse(port.AsSpan(1)) : new Uri($"{scheme}://foo").Port; + + string expectedQuery = rawTarget.Contains('?') ? rawTarget.Substring(rawTarget.IndexOf('?')) : ""; + + string expectedPath = rawTarget.Substring(0, rawTarget.Length - expectedQuery.Length); + + yield return new object[] { uriString, host, expectedPort, expectedPath, expectedQuery }; + } + } + } + } + } + + [Theory] + [MemberData(nameof(DisableCanonicalization_TestData))] + public void DisableCanonicalization_IsRespected(string uriString, string expectedHost, int expectedPort, string expectedPath, string expectedQuery) + { + var options = new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = true }; + + var uri = new Uri(uriString, options); + DoAsserts(uri); + + Assert.True(Uri.TryCreate(uriString, options, out uri)); + DoAsserts(uri); + + void DoAsserts(Uri uri) + { + Assert.Equal(new Uri($"http://{expectedHost}").Host, uri.Host); + Assert.Equal(new Uri($"http://{expectedHost}").IdnHost, uri.IdnHost); + + Assert.Equal(expectedPort, uri.Port); + + Assert.Same(uri.AbsolutePath, uri.AbsolutePath); + Assert.Equal(expectedPath, uri.AbsolutePath); + + Assert.Same(uri.Query, uri.Query); + Assert.Equal(expectedQuery, uri.Query); + + string expectedPathAndQuery = expectedPath + expectedQuery; + Assert.Same(uri.PathAndQuery, uri.PathAndQuery); + Assert.Equal(expectedPathAndQuery, uri.PathAndQuery); + + Assert.Same(uri.Fragment, uri.Fragment); + Assert.Empty(uri.Fragment); // Fragment is always empty if DisableCanonicalization is set + } + } + + [Fact] + public void DisableCanonicalization_OnlyEqualToUrisWithMatchingFlag() + { + const string AbsoluteUri = "http://host"; + const string Path = "/foo"; + + var absolute = new Uri(AbsoluteUri + Path, new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = false }); + var absoluteRaw = new Uri(AbsoluteUri + Path, new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = true }); + NotEqual(absolute, absoluteRaw); + Equal(absolute, absolute); + Equal(absoluteRaw, absoluteRaw); + + var absoluteRawCopy = new Uri(AbsoluteUri + Path, new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = true }); + Equal(absoluteRaw, absoluteRawCopy); + + var absoluteRawDifferentPath = new Uri(AbsoluteUri + "/bar", new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = true }); + NotEqual(absoluteRaw, absoluteRawDifferentPath); + + var absoluteRawSameAuthority = new Uri(AbsoluteUri + ":80" + Path, new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = true }); + Equal(absoluteRaw, absoluteRawSameAuthority); + + static void Equal(Uri left, Uri right) + { + Assert.True(left.Equals(right)); + Assert.True(right.Equals(left)); + Assert.Equal(left.GetHashCode(), right.GetHashCode()); + } + + static void NotEqual(Uri left, Uri right) + { + Assert.False(left.Equals(right)); + Assert.False(right.Equals(left)); + } + } + + private const string FilePathRawData = "//\\A%41 %20\u00F6/.././%5C%2F#%42?%43#%44"; + + public static IEnumerable ImplicitFilePaths_TestData() + { + yield return Entry("C:/"); + yield return Entry("C|/"); + + yield return Entry(@"//foo"); + yield return Entry(@"\/foo"); + yield return Entry(@"/\foo"); + yield return Entry(@"\\foo"); + + if (!PlatformDetection.IsWindows) + { + yield return Entry("/foo"); + } + + static object[] Entry(string filePath) => new object[] { $"{filePath}/{FilePathRawData}" }; + } + + [Theory] + [MemberData(nameof(ImplicitFilePaths_TestData))] + public void DisableCanonicalization_WorksWithFileUris(string implicitFilePath) + { + var options = new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = true }; + + var uri = new Uri(implicitFilePath, options); + DoAsserts(uri); + + Assert.True(Uri.TryCreate(implicitFilePath, options, out uri)); + DoAsserts(uri); + + static void DoAsserts(Uri uri) + { + Assert.True(uri.IsAbsoluteUri); + Assert.True(uri.IsFile); + Assert.Contains(FilePathRawData, uri.AbsolutePath); + Assert.Contains(FilePathRawData, uri.AbsoluteUri); + Assert.Contains(FilePathRawData, uri.ToString()); + } + } + + [Theory] + [InlineData("http")] + [InlineData("https")] + [InlineData("ftp")] + [InlineData("file")] + [InlineData("custom-unknown")] + [InlineData("custom-registered")] + public void DisableCanonicalization_WorksWithDifferentSchemes(string scheme) + { + if (scheme == "custom-registered") + { + scheme += "DisableCanonicalization"; + UriParser.Register(new HttpStyleUriParser(), scheme, defaultPort: Random.Shared.Next(-1, 65536)); + } + + string uriString = $"{scheme}://host/p%41th?a=%42#fragm%45nt"; + var options = new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = true }; + + var referenceUri = new Uri(uriString); + + var uri = new Uri(uriString, options); + DoAsserts(uri); + + Assert.True(Uri.TryCreate(uriString, options, out uri)); + DoAsserts(uri); + + void DoAsserts(Uri uri) + { + Assert.Same(referenceUri.Scheme, uri.Scheme); + Assert.Equal(referenceUri.Host, uri.Host); + Assert.Equal(referenceUri.IdnHost, uri.IdnHost); + Assert.Equal(referenceUri.Authority, uri.Authority); + Assert.Equal(referenceUri.Port, uri.Port); + Assert.Equal(referenceUri.IsDefaultPort, uri.IsDefaultPort); + + string referencePath = "/pAth"; + string referenceQuery = "?a=B"; + string path = "/p%41th"; + string query = "?a=%42#fragm%45nt"; + + if (scheme == "ftp") // No query + { + referencePath += referenceQuery.Replace("?", "%3F"); + path += query; + + referenceQuery = string.Empty; + query = string.Empty; + } + + Assert.Equal(referencePath, referenceUri.AbsolutePath); + Assert.Equal(path, uri.AbsolutePath); + + Assert.Equal(referenceQuery, referenceUri.Query); + Assert.Equal(query, uri.Query); + + Assert.Equal(referencePath + referenceQuery, referenceUri.PathAndQuery); + Assert.Equal(path + query, uri.PathAndQuery); + + Assert.Equal("#fragmEnt", referenceUri.Fragment); + Assert.Empty(uri.Fragment); + + _ = referenceUri.GetComponents(UriComponents.AbsoluteUri, UriFormat.UriEscaped); + Assert.Throws(() => uri.GetComponents(UriComponents.Path, UriFormat.UriEscaped)); + } + } + + [Theory] + [InlineData(UriFormat.UriEscaped)] + [InlineData(UriFormat.Unescaped)] + [InlineData(UriFormat.SafeUnescaped)] + public void DisableCanonicalization_GetComponentsThrowsForPathAndQuery(UriFormat format) + { + var uri = new Uri("http://host/foo?bar=abc#fragment", new UriCreationOptions { DangerousDisablePathAndQueryCanonicalization = true }); + + Assert.Equal("http", uri.GetComponents(UriComponents.Scheme, format)); + Assert.Equal("host", uri.GetComponents(UriComponents.Host, format)); + Assert.Equal("80", uri.GetComponents(UriComponents.StrongPort, format)); + Assert.Empty(uri.GetComponents(UriComponents.Fragment, format)); + + Assert.Throws(() => uri.GetComponents(UriComponents.Path, format)); + Assert.Throws(() => uri.GetComponents(UriComponents.Query, format)); + Assert.Throws(() => uri.GetComponents(UriComponents.PathAndQuery, format)); + Assert.Throws(() => uri.GetComponents(UriComponents.AbsoluteUri, format)); + } + + + private sealed class CustomUriParser : UriParser { } + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 617767603587da..407ed6e5100f43 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -7716,6 +7716,7 @@ protected Uri(System.Runtime.Serialization.SerializationInfo serializationInfo, public Uri(string uriString) { } [System.ObsoleteAttribute("This constructor has been deprecated; the dontEscape parameter is always false. Use Uri(string) instead.")] public Uri(string uriString, bool dontEscape) { } + public Uri(string uriString, in System.UriCreationOptions creationOptions) { } public Uri(string uriString, System.UriKind uriKind) { } public Uri(System.Uri baseUri, string? relativeUri) { } [System.ObsoleteAttribute("This constructor has been deprecated; the dontEscape parameter is always false. Use Uri(Uri, string) instead.")] @@ -7785,6 +7786,7 @@ protected void GetObjectData(System.Runtime.Serialization.SerializationInfo seri protected virtual void Parse() { } void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) { } public override string ToString() { throw null; } + public static bool TryCreate([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? uriString, in System.UriCreationOptions creationOptions, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Uri? result) { throw null; } public static bool TryCreate([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? uriString, System.UriKind uriKind, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Uri? result) { throw null; } public static bool TryCreate(System.Uri? baseUri, string? relativeUri, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Uri? result) { throw null; } public static bool TryCreate(System.Uri? baseUri, System.Uri? relativeUri, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Uri? result) { throw null; } @@ -7821,6 +7823,10 @@ public UriBuilder(System.Uri uri) { } public override int GetHashCode() { throw null; } public override string ToString() { throw null; } } + public partial struct UriCreationOptions + { + public bool DangerousDisablePathAndQueryCanonicalization { readonly get { throw null; } set { } } + } [System.FlagsAttribute] public enum UriComponents { From 63270bbafd15fa2468bfb31cec08421441edfef1 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Wed, 15 Sep 2021 18:32:52 +0200 Subject: [PATCH 2/2] Add /// comments --- .../System.Private.Uri/src/System/Uri.cs | 8 ++++++++ .../src/System/UriCreationOptions.cs | 20 ++++++++++++++++++- .../System.Private.Uri/src/System/UriExt.cs | 7 +++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 806e7014eb604e..7d1b4d72201a38 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -121,6 +121,9 @@ internal enum Flags : ulong IriCanonical = 0x78000000000, UnixPath = 0x100000000000, + /// + /// Disables any validation/normalization past the authority. Fragments will always be empty. GetComponents will throw for Path/Query. + /// DisablePathAndQueryCanonicalization = 0x200000000000, /// @@ -414,6 +417,11 @@ public Uri(string uriString, UriKind uriKind) DebugSetLeftCtor(); } + /// + /// Initializes a new instance of the class with the specified URI and additional . + /// + /// A string that identifies the resource to be represented by the instance. + /// Options that control how the is created and behaves. public Uri(string uriString, in UriCreationOptions creationOptions) { if (uriString is null) diff --git a/src/libraries/System.Private.Uri/src/System/UriCreationOptions.cs b/src/libraries/System.Private.Uri/src/System/UriCreationOptions.cs index b8ec6f874eab27..2d0ee84d08b218 100644 --- a/src/libraries/System.Private.Uri/src/System/UriCreationOptions.cs +++ b/src/libraries/System.Private.Uri/src/System/UriCreationOptions.cs @@ -3,8 +3,26 @@ namespace System { + /// + /// Options that control how a is created and behaves. + /// public struct UriCreationOptions { - public bool DangerousDisablePathAndQueryCanonicalization { readonly get; set; } + private bool _disablePathAndQueryCanonicalization; + + /// + /// Disables validation and normalization of the Path and Query. + /// No transformations of the URI past the Authority will take place. + /// instances created with this option do not support s. + /// may not be used for or . + /// Be aware that disabling canonicalization also means that reserved characters will not be escaped, + /// which may corrupt the HTTP request and makes the application subject to request smuggling. + /// Only set this option if you have ensured that the URI string is already sanitized. + /// + public bool DangerousDisablePathAndQueryCanonicalization + { + readonly get => _disablePathAndQueryCanonicalization; + set => _disablePathAndQueryCanonicalization = value; + } } } diff --git a/src/libraries/System.Private.Uri/src/System/UriExt.cs b/src/libraries/System.Private.Uri/src/System/UriExt.cs index fee23e98ed9ceb..070e11a7f987f3 100644 --- a/src/libraries/System.Private.Uri/src/System/UriExt.cs +++ b/src/libraries/System.Private.Uri/src/System/UriExt.cs @@ -262,6 +262,13 @@ public static bool TryCreate([NotNullWhen(true)] string? uriString, UriKind uriK return e is null && result != null; } + /// + /// Creates a new using the specified instance and . + /// + /// The string representation of the . + /// Options that control how the is created and behaves. + /// The constructed . + /// if the was successfully created; otherwise, . public static bool TryCreate([NotNullWhen(true)] string? uriString, in UriCreationOptions creationOptions, [NotNullWhen(true)] out Uri? result) { if (uriString is null)