| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'characters.dart' as chars; |
| |
| /// Whether [char] is the code for an ASCII letter (uppercase or lowercase). |
| bool isAlphabetic(int char) => |
| chars.lowerA <= (char |= 0x20) && char <= chars.lowerZ; |
| |
| /// Whether [char] is the code for an ASCII digit. |
| bool isNumeric(int char) => char ^ chars.zero <= 9; |
| |
| /// Whether [path] has a URL-formatted Windows drive letter beginning at |
| /// [index]. |
| bool isDriveLetter(String path, int index) => |
| driveLetterEnd(path, index) != index; |
| |
| /// Index after drive letter starting at [index], or [index] if none. |
| /// |
| /// The [path] is a URI-formed path. |
| /// A valid drive letter must be followed by a colon and then either a `/`, |
| /// a `#`, a `?` or the end of the path. |
| /// |
| /// ``` |
| /// d:/abc => 3 |
| /// d:/ => 3 |
| /// d: => 2 |
| /// d:# => 2 |
| /// d:? => 2 |
| /// d => 0 |
| /// d:a => 0 |
| /// ``` |
| /// Same with every `:` replaced by `%3a` or `%3A`, and then the |
| /// returned numbers would be increased by 2. |
| int driveLetterEnd(String path, int index) { |
| if (path.length < index + 2) return index; |
| if (!isAlphabetic(path.codeUnitAt(index))) return index; |
| final colonChar = path.codeUnitAt(index + 1); |
| final int indexAfter; |
| if (colonChar == chars.colon) { |
| indexAfter = index + 2; // After '<letter>:' |
| } else if (colonChar == chars.percent && |
| path.length >= index + 4 && |
| path.codeUnitAt(index + 2) == (chars.zero + 3) && |
| (path.codeUnitAt(index + 3) | 0x20) == chars.lowerA) { |
| indexAfter = index + 4; // After '<letter>%3A'. |
| } else { |
| return index; |
| } |
| if (path.length == indexAfter) return indexAfter; |
| final nextChar = path.codeUnitAt(indexAfter); |
| if (nextChar == chars.slash) return indexAfter + 1; |
| if (nextChar == chars.hash || nextChar == chars.question) return indexAfter; |
| return index; |
| } |
| |
| /// Position after a leading URL scheme in [path], if any. |
| /// |
| /// If [path] starts with a valid URL scheme at position [index], then returns |
| /// then index after the following colon, otherwise returns [index]. |
| int endOfScheme(String path, int index) { |
| if (index >= path.length) return index; |
| final firstChar = path.codeUnitAt(index); |
| if (!isAlphabetic(firstChar)) return index; |
| for (var i = index + 1; i < path.length; i++) { |
| final codeUnit = path.codeUnitAt(i); |
| if (!isAlphabetic(codeUnit) && |
| !isNumeric(codeUnit) && |
| codeUnit != chars.plus && |
| codeUnit != chars.minus && |
| codeUnit != chars.period) { |
| if (codeUnit == chars.colon) { |
| return i + 1; |
| } |
| break; |
| } |
| } |
| return index; |
| } |
| |
| /// Checks if [path] starts with `"file:"`, case insensitively. |
| bool startsWithFileColon(String path) { |
| if (path.length < 5) return false; |
| return path.length >= 5 && |
| path.codeUnitAt(4) == chars.colon && |
| (path.codeUnitAt(0) | 0x20) == chars.lowerF && |
| (path.codeUnitAt(1) | 0x20) == chars.lowerI && |
| (path.codeUnitAt(2) | 0x20) == chars.lowerL && |
| (path.codeUnitAt(3) | 0x20) == chars.lowerE; |
| } |
| |
| /// Position after a URI authority part at [index], if any. |
| /// |
| /// The [path] must be a URI formatted text. |
| /// |
| /// If an authority part is found, meaning that [path] starts with `//` at |
| /// [index], the result is the position of the first |
| /// non-authority character, which must be one of `/`, `?` or `#`, |
| /// or the end of the `path`. |
| /// Otherwise the result is [index]. |
| int authorityEnd(String path, int index) { |
| if (!path.startsWith('//', index)) return index; |
| index += 2; |
| while (index < path.length) { |
| final codeUnit = path.codeUnitAt(index); |
| if (codeUnit == chars.question || codeUnit == chars.hash) break; |
| if (codeUnit == chars.slash) break; |
| index++; |
| } |
| return index; |
| } |
| |
| String removeQueryFragment(String pathSegment) { |
| for (var i = 0; i < pathSegment.length; i++) { |
| final codeUnit = pathSegment.codeUnitAt(i); |
| if (codeUnit == chars.question || codeUnit == chars.hash) { |
| return pathSegment.substring(0, i); |
| } |
| } |
| return pathSegment; |
| } |