| File: PathTokenizer.cs | Web Access |
| Project: src\aspnetcore\src\Http\Routing\src\Microsoft.AspNetCore.Routing.csproj (Microsoft.AspNetCore.Routing) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable using System.Collections; using System.Diagnostics; #if !COMPONENTS using Microsoft.AspNetCore.Http; #else using Microsoft.AspNetCore.Components.Routing; #endif using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Routing; internal struct PathTokenizer : IReadOnlyList<StringSegment> { private readonly string _path; private int _count; public PathTokenizer(PathString path) { _path = path.Value; _count = -1; } public int Count { get { if (_count == -1) { // We haven't computed the real count of segments yet. if (_path.Length == 0) { // The empty string has length of 0. _count = 0; return _count; } // A string of length 1 must be "/" - all PathStrings start with '/' if (_path.Length == 1) { // We treat this as empty - there's nothing to parse here for routing, because routing ignores // a trailing slash. Debug.Assert(_path[0] == '/'); _count = 0; return _count; } // This is a non-trivial PathString // Since a non-empty PathString must begin with a `/`, we can just count the number of occurrences // of `/` to find the number of segments. However, we don't look at the last character, because // routing ignores a trailing slash. _count = _path.AsSpan(0, _path.Length - 1).Count('/'); } return _count; } } public StringSegment this[int index] { get { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count); var currentSegmentIndex = 0; var currentSegmentStart = 1; // Skip the first `/`. var delimiterIndex = 1; while ((delimiterIndex = _path.IndexOf('/', delimiterIndex)) != -1) { if (currentSegmentIndex++ == index) { return new StringSegment(_path, currentSegmentStart, delimiterIndex - currentSegmentStart); } else { currentSegmentStart = delimiterIndex + 1; delimiterIndex++; } } // If we get here we're at the end of the string. The implementation of .Count should protect us // from these cases. Debug.Assert(_path[_path.Length - 1] != '/'); Debug.Assert(currentSegmentIndex == index); return new StringSegment(_path, currentSegmentStart, _path.Length - currentSegmentStart); } } public Enumerator GetEnumerator() { return new Enumerator(this); } IEnumerator<StringSegment> IEnumerable<StringSegment>.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public struct Enumerator : IEnumerator<StringSegment> { private readonly string _path; private int _index; private int _length; public Enumerator(PathTokenizer tokenizer) { _path = tokenizer._path; _index = -1; _length = -1; } public StringSegment Current { get { return new StringSegment(_path, _index, _length); } } object IEnumerator.Current { get { return Current; } } public void Dispose() { } public bool MoveNext() { if (_path == null || _path.Length <= 1) { return false; } if (_index == -1) { // Skip the first `/`. _index = 1; } else { // Skip to the end of the previous segment + the separator. _index += _length + 1; } if (_index >= _path.Length) { // We're at the end return false; } var delimiterIndex = _path.IndexOf('/', _index); if (delimiterIndex != -1) { _length = delimiterIndex - _index; return true; } // We might have some trailing text after the last separator. if (_path[_path.Length - 1] == '/') { // If the last char is a '/' then it's just a trailing slash, we don't have another segment. return false; } else { _length = _path.Length - _index; return true; } } public void Reset() { _index = -1; _length = -1; } } }