blob: 0e427a9096152eba7f60b7828a6896f866857678 [file] [log] [blame]
// Copyright (c) 2013, 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;
import '../internal_style.dart';
import '../utils.dart';
/// The style for URL paths.
class UrlStyle extends InternalStyle {
@override
final name = 'url';
@override
final separator = '/';
final separators = const ['/'];
// Deprecated properties.
@override
final separatorPattern = RegExp(r'/');
@override
final needsSeparatorPattern = RegExp(r'(^[a-zA-Z][-+.a-zA-Z\d]*://|[^/])$');
@override
final rootPattern = RegExp(r'[a-zA-Z][-+.a-zA-Z\d]*://[^/]*');
@override
final relativeRootPattern = RegExp(r'^/');
@override
bool containsSeparator(String path) => path.contains('/');
@override
bool isSeparator(int codeUnit) => codeUnit == chars.slash;
@override
bool needsSeparator(String path) {
if (path.isEmpty) return false;
// A URL that doesn't end in "/" always needs a separator.
if (!isSeparator(path.codeUnitAt(path.length - 1))) return true;
// A URI that's just "scheme://" needs an extra separator, despite ending
// with "/".
return path.endsWith('://') && rootLength(path) == path.length;
}
@override
int rootLength(String path, {bool withDrive = false}) {
if (path.isEmpty) return 0;
// Find scheme. Recognize `file:` scheme specifically,
// since it is required for `withDrive` to apply.
final int afterScheme;
if (withDrive && startsWithFileColon(path)) {
// Check for authority, then drive letter.
const fileSchemeLength = 'file:'.length;
afterScheme = fileSchemeLength;
} else {
withDrive = false;
afterScheme = endOfScheme(path, 0);
}
// If there is a scheme, include authority if any.
// If no scheme, a leading `//` is considered part of a path, unlike
// how a URI would parse it. (For backwards compatibility)
final afterAuthority =
afterScheme > 0 ? authorityEnd(path, afterScheme) : 0;
// If no scheme and no authority, include leading `/` in root.
// If scheme or authority, do not include a first `/` of the path in root.
// If scheme and no authority, include first segment of path in root,
// but do not look for drive letters (even if scheme is `file:`).
// If `file:` scheme and authority (`//` or `//...`), and `withDrive`,
// look for drive letter at start of path (after first slash).
if (afterAuthority == path.length) return afterAuthority;
final nextChar = path.codeUnitAt(afterAuthority);
final int afterPathRoot;
if (isSeparator(nextChar)) {
afterPathRoot = afterAuthority + 1;
if (withDrive && afterAuthority > afterScheme) {
final afterDriveLetter = driveLetterEnd(path, afterPathRoot);
if (afterDriveLetter > afterPathRoot) return afterDriveLetter;
}
if (afterAuthority == 0) return afterPathRoot;
return afterAuthority;
}
if (afterAuthority > afterScheme) {
// Has authority, not followed by `/`, so empty path.
// Character after authority must be `#` or `?`, or end of path.
return afterAuthority;
}
if (afterScheme > 0) {
// If scheme, no authority, no leading `/` in path, include next segment
// in root (and not `/`).
var char = nextChar;
var i = afterAuthority;
while (
char != chars.hash && char != chars.question && char != chars.slash) {
i++;
if (i == path.length) break;
char = path.codeUnitAt(i);
}
return i; // Never checks for drive letter after non-authority.
}
// No scheme, no authority, path does not start with `/`.
return 0;
}
@override
bool isRootRelative(String path) =>
path.isNotEmpty &&
isSeparator(path.codeUnitAt(0)) &&
(path.length < 2 || !isSeparator(path.codeUnitAt(1)));
@override
String? getRelativeRoot(String path) => isRootRelative(path) ? '/' : null;
@override
String pathFromUri(Uri uri) => uri.toString();
@override
Uri relativePathToUri(String path) => Uri.parse(path);
@override
Uri absolutePathToUri(String path) => Uri.parse(path);
}