Line data Source code
1 : // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 : // for details. All rights reserved. Use of this source code is governed by a 3 : // BSD-style license that can be found in the LICENSE file. 4 : 5 : import '../characters.dart' as chars; 6 : import '../internal_style.dart'; 7 : import '../parsed_path.dart'; 8 : import '../utils.dart'; 9 : 10 : // `0b100000` can be bitwise-ORed with uppercase ASCII letters to get their 11 : // lowercase equivalents. 12 : const _asciiCaseBit = 0x20; 13 : 14 : /// The style for Windows paths. 15 : class WindowsStyle extends InternalStyle { 16 11 : WindowsStyle(); 17 : 18 : @override 19 : final name = 'windows'; 20 : @override 21 : final separator = '\\'; 22 : final separators = const ['/', '\\']; 23 : 24 : // Deprecated properties. 25 : 26 : @override 27 : final separatorPattern = RegExp(r'[/\\]'); 28 : @override 29 : final needsSeparatorPattern = RegExp(r'[^/\\]$'); 30 : @override 31 : final rootPattern = RegExp(r'^(\\\\[^\\]+\\[^\\/]+|[a-zA-Z]:[/\\])'); 32 : @override 33 : final relativeRootPattern = RegExp(r'^[/\\](?![/\\])'); 34 : 35 0 : @override 36 0 : bool containsSeparator(String path) => path.contains('/'); 37 : 38 0 : @override 39 : bool isSeparator(int codeUnit) => 40 0 : codeUnit == chars.slash || codeUnit == chars.backslash; 41 : 42 0 : @override 43 : bool needsSeparator(String path) { 44 0 : if (path.isEmpty) return false; 45 0 : return !isSeparator(path.codeUnitAt(path.length - 1)); 46 : } 47 : 48 0 : @override 49 : int rootLength(String path, {bool withDrive = false}) { 50 0 : if (path.isEmpty) return 0; 51 0 : if (path.codeUnitAt(0) == chars.slash) return 1; 52 0 : if (path.codeUnitAt(0) == chars.backslash) { 53 0 : if (path.length < 2 || path.codeUnitAt(1) != chars.backslash) return 1; 54 : // The path is a network share. Search for up to two '\'s, as they are 55 : // the server and share - and part of the root part. 56 0 : var index = path.indexOf('\\', 2); 57 0 : if (index > 0) { 58 0 : index = path.indexOf('\\', index + 1); 59 0 : if (index > 0) return index; 60 : } 61 0 : return path.length; 62 : } 63 : // If the path is of the form 'C:/' or 'C:\', with C being any letter, it's 64 : // a root part. 65 0 : if (path.length < 3) return 0; 66 : // Check for the letter. 67 0 : if (!isAlphabetic(path.codeUnitAt(0))) return 0; 68 : // Check for the ':'. 69 0 : if (path.codeUnitAt(1) != chars.colon) return 0; 70 : // Check for either '/' or '\'. 71 0 : if (!isSeparator(path.codeUnitAt(2))) return 0; 72 : return 3; 73 : } 74 : 75 0 : @override 76 0 : bool isRootRelative(String path) => rootLength(path) == 1; 77 : 78 0 : @override 79 : String? getRelativeRoot(String path) { 80 0 : final length = rootLength(path); 81 0 : if (length == 1) return path[0]; 82 : return null; 83 : } 84 : 85 0 : @override 86 : String pathFromUri(Uri uri) { 87 0 : if (uri.scheme != '' && uri.scheme != 'file') { 88 0 : throw ArgumentError("Uri $uri must have scheme 'file:'."); 89 : } 90 : 91 0 : var path = uri.path; 92 0 : if (uri.host == '') { 93 : // Drive-letter paths look like "file:///C:/path/to/file". The 94 : // replaceFirst removes the extra initial slash. Otherwise, leave the 95 : // slash to match IE's interpretation of "/foo" as a root-relative path. 96 0 : if (path.length >= 3 && path.startsWith('/') && isDriveLetter(path, 1)) { 97 0 : path = path.replaceFirst('/', ''); 98 : } 99 : } else { 100 : // Network paths look like "file://hostname/path/to/file". 101 0 : path = '\\\\${uri.host}$path'; 102 : } 103 0 : return Uri.decodeComponent(path.replaceAll('/', '\\')); 104 : } 105 : 106 0 : @override 107 : Uri absolutePathToUri(String path) { 108 0 : final parsed = ParsedPath.parse(path, this); 109 0 : if (parsed.root!.startsWith(r'\\')) { 110 : // Network paths become "file://server/share/path/to/file". 111 : 112 : // The root is of the form "\\server\share". We want "server" to be the 113 : // URI host, and "share" to be the first element of the path. 114 0 : final rootParts = parsed.root!.split('\\').where((part) => part != ''); 115 0 : parsed.parts.insert(0, rootParts.last); 116 : 117 0 : if (parsed.hasTrailingSeparator) { 118 : // If the path has a trailing slash, add a single empty component so the 119 : // URI has a trailing slash as well. 120 0 : parsed.parts.add(''); 121 : } 122 : 123 0 : return Uri( 124 0 : scheme: 'file', host: rootParts.first, pathSegments: parsed.parts); 125 : } else { 126 : // Drive-letter paths become "file:///C:/path/to/file". 127 : 128 : // If the path is a bare root (e.g. "C:\"), [parsed.parts] will currently 129 : // be empty. We add an empty component so the URL constructor produces 130 : // "file:///C:/", with a trailing slash. We also add an empty component if 131 : // the URL otherwise has a trailing slash. 132 0 : if (parsed.parts.isEmpty || parsed.hasTrailingSeparator) { 133 0 : parsed.parts.add(''); 134 : } 135 : 136 : // Get rid of the trailing "\" in "C:\" because the URI constructor will 137 : // add a separator on its own. 138 0 : parsed.parts 139 0 : .insert(0, parsed.root!.replaceAll('/', '').replaceAll('\\', '')); 140 : 141 0 : return Uri(scheme: 'file', pathSegments: parsed.parts); 142 : } 143 : } 144 : 145 0 : @override 146 : bool codeUnitsEqual(int codeUnit1, int codeUnit2) { 147 0 : if (codeUnit1 == codeUnit2) return true; 148 : 149 : /// Forward slashes and backslashes are equivalent on Windows. 150 0 : if (codeUnit1 == chars.slash) return codeUnit2 == chars.backslash; 151 0 : if (codeUnit1 == chars.backslash) return codeUnit2 == chars.slash; 152 : 153 : // If this check fails, the code units are definitely different. If it 154 : // succeeds *and* either codeUnit is an ASCII letter, they're equivalent. 155 0 : if (codeUnit1 ^ codeUnit2 != _asciiCaseBit) return false; 156 : 157 : // Now we just need to verify that one of the code units is an ASCII letter. 158 0 : final upperCase1 = codeUnit1 | _asciiCaseBit; 159 0 : return upperCase1 >= chars.lowerA && upperCase1 <= chars.lowerZ; 160 : } 161 : 162 0 : @override 163 : bool pathsEqual(String path1, String path2) { 164 : if (identical(path1, path2)) return true; 165 0 : if (path1.length != path2.length) return false; 166 0 : for (var i = 0; i < path1.length; i++) { 167 0 : if (!codeUnitsEqual(path1.codeUnitAt(i), path2.codeUnitAt(i))) { 168 : return false; 169 : } 170 : } 171 : return true; 172 : } 173 : 174 0 : @override 175 : int canonicalizeCodeUnit(int codeUnit) { 176 0 : if (codeUnit == chars.slash) return chars.backslash; 177 0 : if (codeUnit < chars.upperA) return codeUnit; 178 0 : if (codeUnit > chars.upperZ) return codeUnit; 179 0 : return codeUnit | _asciiCaseBit; 180 : } 181 : 182 0 : @override 183 0 : String canonicalizePart(String part) => part.toLowerCase(); 184 : }