@@ -43,32 +43,89 @@ class UrlStyle extends InternalStyle {
4343 return path.endsWith ('://' ) && rootLength (path) == path.length;
4444 }
4545
46+ /// Checks if [path] starts with `"file:"` , case insensitively.
47+ static bool _startsWithFileColon (String path) {
48+ if (path.length < 5 ) return false ;
49+ const f = 0x66 ;
50+ const i = 0x69 ;
51+ const l = 0x6c ;
52+ const e = 0x65 ;
53+ return path.codeUnitAt (4 ) == chars.colon &&
54+ (path.codeUnitAt (0 ) | 0x20 ) == f &&
55+ (path.codeUnitAt (1 ) | 0x20 ) == i &&
56+ (path.codeUnitAt (2 ) | 0x20 ) == l &&
57+ (path.codeUnitAt (3 ) | 0x20 ) == e;
58+ }
59+
4660 @override
4761 int rootLength (String path, {bool withDrive = false }) {
4862 if (path.isEmpty) return 0 ;
49- if (isSeparator (path. codeUnitAt ( 0 ))) return 1 ;
50-
51- for ( var i = 0 ; i < path.length; i ++ ) {
52- final codeUnit = path.codeUnitAt (i );
53- if (isSeparator (codeUnit )) return 0 ;
54- if (codeUnit == chars.colon) {
55- if ( i == 0 ) return 0 ;
56-
57- // The root part is up until the next '/', or the full path. Skip ':'
58- // (and '//' if it exists) and search for '/' after that.
59- if (path. startsWith ( '//' , i + 1 )) i += 3 ;
60- final index = path. indexOf ( '/' , i);
61- if (index <= 0 ) return path.length;
62-
63- // file: URLs sometimes consider Windows drive letters part of the root.
64- // See https://url.spec.whatwg.org/#file-slash-state.
65- if ( ! withDrive || path.length < index + 3 ) return index ;
66- if ( ! path. startsWith ( 'file://' )) return index;
67- return driveLetterEnd (path, index + 1 ) ?? index ;
63+ if (withDrive && _startsWithFileColon (path)) {
64+ return _rootAuthorityLength (path, 5 , withDrive : true );
65+ }
66+ final firstChar = path.codeUnitAt (0 );
67+ if (chars. isLetter (firstChar )) {
68+ // Check if starting with scheme or drive letter.
69+ for ( var i = 1 ; i < path.length; i ++ ) {
70+ final codeUnit = path. codeUnitAt (i);
71+ if (chars. isLetter (codeUnit) ||
72+ chars. isDigit (codeUnit) ||
73+ codeUnit == chars.plus ||
74+ codeUnit == chars.minus ||
75+ codeUnit == chars.period) {
76+ continue ;
77+ }
78+ if (codeUnit == chars.colon) {
79+ return _rootAuthorityLength ( path, i + 1 , withDrive : false ) ;
80+ }
81+ break ;
6882 }
83+ return 0 ;
6984 }
85+ return _rootAuthorityLength (path, 0 , withDrive: false );
86+ }
7087
71- return 0 ;
88+ /// Checks for authority part at start or after scheme.
89+ ///
90+ /// If found, includes this in the root length.
91+ ///
92+ /// Includes an authority starting at `//` until the next `/` , `?` or `#` ,
93+ /// or the end of the path.
94+ int _rootAuthorityLength (String path, int index, {required bool withDrive}) {
95+ if (path.startsWith ('//' , index)) {
96+ index += 2 ;
97+ while (true ) {
98+ if (index == path.length) return index;
99+ final codeUnit = path.codeUnitAt (index);
100+ if (codeUnit == chars.question || codeUnit == chars.hash) return index;
101+ index++ ;
102+ if (isSeparator (codeUnit)) break ;
103+ }
104+ }
105+ if (withDrive) return _withDrive (path, index);
106+ return index;
107+ }
108+
109+ /// Checks for `[a-z]:/` , or `[a-z]:` when followed by `?` or `#` or nothing.
110+ ///
111+ /// If found, includes this in the root length.
112+ int _withDrive (String path, int index) {
113+ final afterDrive = index + 2 ;
114+ if (path.length < afterDrive ||
115+ ! chars.isLetter (path.codeUnitAt (index)) ||
116+ path.codeUnitAt (index + 1 ) != chars.colon) {
117+ return index;
118+ }
119+ if (path.length == afterDrive) return afterDrive;
120+ final nextChar = path.codeUnitAt (afterDrive);
121+ if (nextChar == chars.slash) {
122+ // Include following slash in root.
123+ return afterDrive + 1 ;
124+ }
125+ if (nextChar == chars.question || nextChar == chars.hash) {
126+ return afterDrive;
127+ }
128+ return index;
72129 }
73130
74131 @override
0 commit comments