Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 58 additions & 7 deletions src/libraries/System.Private.Uri/src/System/Uri.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2273,7 +2273,7 @@ private unsafe void CreateUriInfo(Flags cF)
if ((cF & Flags.ImplicitFile) != 0)
{
idx = 0;
while (UriHelper.IsLWS(_string[idx]))
while (idx < _string.Length && UriHelper.IsLWS(_string[idx]))
{
++idx;
++info.Offset.Scheme;
Expand All @@ -2283,8 +2283,18 @@ private unsafe void CreateUriInfo(Flags cF)
{
// For implicit file AND Unc only
idx += 2;
// Ensure idx doesn't exceed string length after increment
if (idx > _string.Length)
{
throw GetException(ParsingError.BadHostName)!;
}
//skip any other slashes (compatibility with V1.0 parser)
int end = (int)(cF & Flags.IndexMask);
// If end exceeds string length, URI is malformed (e.g., bidi chars were stripped)
if (end > _string.Length)
{
throw GetException(ParsingError.BadHostName)!;
}
while (idx < end && (_string[idx] == '/' || _string[idx] == '\\'))
{
++idx;
Expand All @@ -2296,22 +2306,33 @@ private unsafe void CreateUriInfo(Flags cF)
// This is NOT an ImplicitFile uri
idx = _syntax.SchemeName.Length;

while (_string[idx++] != ':')
while (idx < _string.Length && _string[idx++] != ':')
{
++info.Offset.Scheme;
}

if ((cF & Flags.AuthorityFound) != 0)
{
if (_string[idx] == '\\' || _string[idx + 1] == '\\')
if (idx < _string.Length && (idx + 1) < _string.Length &&
(_string[idx] == '\\' || _string[idx + 1] == '\\'))
notCanonicalScheme = true;

idx += 2;
// Ensure idx doesn't exceed string length after increment
if (idx > _string.Length)
{
throw GetException(ParsingError.BadHostName)!;
}
if ((cF & (Flags.UncPath | Flags.DosPath)) != 0)
{
// Skip slashes if it was allowed during ctor time
// NB: Today this is only allowed if a Unc or DosPath was found after the scheme
int end = (int)(cF & Flags.IndexMask);
// If end exceeds string length, URI is malformed (e.g., bidi chars were stripped)
if (end > _string.Length)
{
throw GetException(ParsingError.BadHostName)!;
}
while (idx < end && (_string[idx] == '/' || _string[idx] == '\\'))
{
notCanonicalScheme = true;
Expand All @@ -2331,7 +2352,13 @@ private unsafe void CreateUriInfo(Flags cF)
)
{
//there is no Authority component defined
info.Offset.User = (int)(cF & Flags.IndexMask);
int pathIndex = (int)(cF & Flags.IndexMask);
// Validate index is within string bounds
if (pathIndex > _string.Length)
{
throw GetException(ParsingError.BadHostName)!;
}
info.Offset.User = pathIndex;
info.Offset.Host = info.Offset.User;
info.Offset.Path = info.Offset.User;
cF &= ~Flags.IndexMask;
Expand All @@ -2348,19 +2375,29 @@ private unsafe void CreateUriInfo(Flags cF)
if (HostType == Flags.BasicHostType)
{
info.Offset.Host = idx;
info.Offset.Path = (int)(cF & Flags.IndexMask);
int pathIndex = (int)(cF & Flags.IndexMask);
// Validate index is within string bounds
if (pathIndex > _string.Length)
{
throw GetException(ParsingError.BadHostName)!;
}
info.Offset.Path = pathIndex;
cF &= ~Flags.IndexMask;
goto Done;
}

if ((cF & Flags.HasUserInfo) != 0)
{
// we previously found a userinfo, get it again
while (_string[idx] != '@')
while (idx < _string.Length && _string[idx] != '@')
{
++idx;
}
// Only increment if we found '@' within bounds
if (idx < _string.Length)
{
++idx;
}
++idx;
info.Offset.Host = idx;
}
else
Expand All @@ -2371,6 +2408,20 @@ private unsafe void CreateUriInfo(Flags cF)
//Now reload the end of the parsed host
idx = (int)(cF & Flags.IndexMask);

// Handle cases where _string was modified during parsing (percent-decoding, bidi removal).
// If idx exceeds _string.Length AND we have a file:// URI with UncPath, this indicates
// bidi character removal created a malformed URI (empty host). Otherwise, clamp for IRI processing.
if (idx > _string.Length)
{
if (StaticIsFile(_syntax) && StaticInFact(cF, Flags.UncPath))
{
// File UNC paths with out-of-bounds indices indicate malformed URIs from bidi removal
throw GetException(ParsingError.BadHostName)!;
}
// For other cases (e.g., percent-decoding in IRI), clamp to string length
idx = _string.Length;
}

//From now on we do not need IndexMask bits, and reuse the space for X_NotCanonical flags
//clear them now
cF &= ~Flags.IndexMask;
Expand Down
16 changes: 16 additions & 0 deletions src/libraries/System.Private.Uri/tests/FunctionalTests/UriTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1001,5 +1001,21 @@ static void Test(string uriString)
Assert.Throws<OutOfMemoryException>(() => uri.AbsoluteUri);
}
}

[Theory]
[InlineData("/\\//")]
[InlineData("\\/\u200e")]
[InlineData("/\\\\-\u0100\r")]
[InlineData("\\\\\\\\\\")]
[InlineData("\\\\\u200E")]
[InlineData("\\\\\u200E:1234")]
[InlineData("\\\\\u200E//")]
[InlineData("\\\\\u200E//ab")]
public static void InvalidUriWithBidiControlCharacters_ThrowsUriFormatException(string uriString)
{
// These URIs should throw UriFormatException, not IndexOutOfRangeException
// Issue: https://github.com/dotnet/runtime/issues/18640
Assert.Throws<UriFormatException>(() => new Uri(uriString, UriKind.Absolute));
}
}
}
Loading