11import 'package:meta/meta.dart' ;
22
3- import 'cache.dart' ;
4- import 'lru_cache.dart' ;
5-
63/// Represents a URL path that has been normalized.
74///
85/// Normalization includes:
96/// - Resolving `.` and `..` segments.
107/// - Removing empty segments caused by multiple consecutive slashes.
118/// - Ensuring the path starts with a `/` .
129///
13- /// Instances created from a path string are interned using an LRU cache for
14- /// efficiency, so identical paths will often share the same object instance.
15- /// Segment-built instances ([fromSegments] , [fromUri] ) are never interned: a
16- /// string cache key cannot tell a separator inside a segment from a real one.
17- /// Equality compares segments, so this affects allocation only.
10+ /// Equality and [hashCode] are derived from [segments] , so paths with the same
11+ /// segments are equal regardless of how they were built. Construction does no
12+ /// caching.
1813@immutable
1914class NormalizedPath {
20- /// Cache of interned instances.
21- ///
22- /// Defaults to an [LruCache] with 10,000 entries. Can be replaced with any
23- /// [Cache] implementation to tune caching behavior:
24- ///
25- /// ```dart
26- /// // Disable caching for high-cardinality workloads
27- /// NormalizedPath.interned = NoCache();
28- ///
29- /// // Use a larger cache
30- /// NormalizedPath.interned = LruCache(50000);
31- /// ```
32- static Cache <String , NormalizedPath > interned =
33- LruCache <String , NormalizedPath >(10000 );
34-
3515 /// The individual segments of the normalized path.
3616 /// For example, the path `/a/b/c` would have segments `['a', 'b', 'c']` .
3717 final List <String > segments;
@@ -44,20 +24,12 @@ class NormalizedPath {
4424
4525 /// Creates a [NormalizedPath] from a given [path] string.
4626 ///
47- /// The provided [path] will be normalized by resolving `.` and `..` segments
48- /// and removing empty segments. The resulting [NormalizedPath] instance may be
49- /// retrieved from a cache if an identical normalized path has been created
50- /// recently.
51- factory NormalizedPath (final String path) {
52- var result = interned[path];
53- if (result == null ) {
54- result = NormalizedPath ._(_normalizeSegments (path.split ('/' )));
55- // intern for both normalized path and path
56- result = interned[result.path] ?? = result;
57- interned[path] = result; // cache for original path as well
58- }
59- return result;
60- }
27+ /// The provided [path] is split on `/` and normalized by resolving `.` and
28+ /// `..` segments and removing empty ones. The path is not percent-decoded,
29+ /// so an encoded separator such as `%2F` stays literal within its segment;
30+ /// use [NormalizedPath.fromUri] to derive a path from a request.
31+ factory NormalizedPath (final String path) =>
32+ NormalizedPath ._(_normalizeSegments (path.split ('/' )));
6133
6234 /// Creates a [NormalizedPath] from segments that have already been split.
6335 ///
@@ -71,14 +43,18 @@ class NormalizedPath {
7143
7244 /// Creates a [NormalizedPath] from the path of [url] .
7345 ///
74- /// This is the correct way to derive a path from a request. It reads
75- /// [Uri.pathSegments] , which splits on the separator and only then decodes
76- /// each segment, so an encoded separator such as `%2F` stays inside its
77- /// segment. Building from [Uri.path] instead would decode first and then
78- /// split, introducing separators that no proxy in front of the server ever
79- /// saw.
80- factory NormalizedPath .fromUri (final Uri url) =>
81- NormalizedPath .fromSegments (url.pathSegments);
46+ /// [Uri.pathSegments] splits on the separator before decoding, so an encoded
47+ /// separator (`%2F` ) stays within its segment.
48+ factory NormalizedPath .fromUri (final Uri url) {
49+ final segments = url.pathSegments;
50+ // Reuse the unmodifiable pathSegments if clean
51+ for (final segment in segments) {
52+ if (segment.isEmpty || segment == '.' || segment == '..' ) {
53+ return NormalizedPath ._(_normalizeSegments (segments));
54+ }
55+ }
56+ return NormalizedPath ._(segments);
57+ }
8258
8359 /// Normalizes [segments] by resolving `.` and `..` and dropping empty ones.
8460 static List <String > _normalizeSegments (final Iterable <String > segments) {
0 commit comments