Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/common/extpath.ts
3291 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CharCode } from './charCode.js';
7
import { isAbsolute, join, normalize, posix, sep } from './path.js';
8
import { isWindows } from './platform.js';
9
import { equalsIgnoreCase, rtrim, startsWithIgnoreCase } from './strings.js';
10
import { isNumber } from './types.js';
11
12
export function isPathSeparator(code: number) {
13
return code === CharCode.Slash || code === CharCode.Backslash;
14
}
15
16
/**
17
* Takes a Windows OS path and changes backward slashes to forward slashes.
18
* This should only be done for OS paths from Windows (or user provided paths potentially from Windows).
19
* Using it on a Linux or MaxOS path might change it.
20
*/
21
export function toSlashes(osPath: string) {
22
return osPath.replace(/[\\/]/g, posix.sep);
23
}
24
25
/**
26
* Takes a Windows OS path (using backward or forward slashes) and turns it into a posix path:
27
* - turns backward slashes into forward slashes
28
* - makes it absolute if it starts with a drive letter
29
* This should only be done for OS paths from Windows (or user provided paths potentially from Windows).
30
* Using it on a Linux or MaxOS path might change it.
31
*/
32
export function toPosixPath(osPath: string) {
33
if (osPath.indexOf('/') === -1) {
34
osPath = toSlashes(osPath);
35
}
36
if (/^[a-zA-Z]:(\/|$)/.test(osPath)) { // starts with a drive letter
37
osPath = '/' + osPath;
38
}
39
return osPath;
40
}
41
42
/**
43
* Computes the _root_ this path, like `getRoot('c:\files') === c:\`,
44
* `getRoot('files:///files/path') === files:///`,
45
* or `getRoot('\\server\shares\path') === \\server\shares\`
46
*/
47
export function getRoot(path: string, sep: string = posix.sep): string {
48
if (!path) {
49
return '';
50
}
51
52
const len = path.length;
53
const firstLetter = path.charCodeAt(0);
54
if (isPathSeparator(firstLetter)) {
55
if (isPathSeparator(path.charCodeAt(1))) {
56
// UNC candidate \\localhost\shares\ddd
57
// ^^^^^^^^^^^^^^^^^^^
58
if (!isPathSeparator(path.charCodeAt(2))) {
59
let pos = 3;
60
const start = pos;
61
for (; pos < len; pos++) {
62
if (isPathSeparator(path.charCodeAt(pos))) {
63
break;
64
}
65
}
66
if (start !== pos && !isPathSeparator(path.charCodeAt(pos + 1))) {
67
pos += 1;
68
for (; pos < len; pos++) {
69
if (isPathSeparator(path.charCodeAt(pos))) {
70
return path.slice(0, pos + 1) // consume this separator
71
.replace(/[\\/]/g, sep);
72
}
73
}
74
}
75
}
76
}
77
78
// /user/far
79
// ^
80
return sep;
81
82
} else if (isWindowsDriveLetter(firstLetter)) {
83
// check for windows drive letter c:\ or c:
84
85
if (path.charCodeAt(1) === CharCode.Colon) {
86
if (isPathSeparator(path.charCodeAt(2))) {
87
// C:\fff
88
// ^^^
89
return path.slice(0, 2) + sep;
90
} else {
91
// C:
92
// ^^
93
return path.slice(0, 2);
94
}
95
}
96
}
97
98
// check for URI
99
// scheme://authority/path
100
// ^^^^^^^^^^^^^^^^^^^
101
let pos = path.indexOf('://');
102
if (pos !== -1) {
103
pos += 3; // 3 -> "://".length
104
for (; pos < len; pos++) {
105
if (isPathSeparator(path.charCodeAt(pos))) {
106
return path.slice(0, pos + 1); // consume this separator
107
}
108
}
109
}
110
111
return '';
112
}
113
114
/**
115
* Check if the path follows this pattern: `\\hostname\sharename`.
116
*
117
* @see https://msdn.microsoft.com/en-us/library/gg465305.aspx
118
* @return A boolean indication if the path is a UNC path, on none-windows
119
* always false.
120
*/
121
export function isUNC(path: string): boolean {
122
if (!isWindows) {
123
// UNC is a windows concept
124
return false;
125
}
126
127
if (!path || path.length < 5) {
128
// at least \\a\b
129
return false;
130
}
131
132
let code = path.charCodeAt(0);
133
if (code !== CharCode.Backslash) {
134
return false;
135
}
136
137
code = path.charCodeAt(1);
138
139
if (code !== CharCode.Backslash) {
140
return false;
141
}
142
143
let pos = 2;
144
const start = pos;
145
for (; pos < path.length; pos++) {
146
code = path.charCodeAt(pos);
147
if (code === CharCode.Backslash) {
148
break;
149
}
150
}
151
152
if (start === pos) {
153
return false;
154
}
155
156
code = path.charCodeAt(pos + 1);
157
158
if (isNaN(code) || code === CharCode.Backslash) {
159
return false;
160
}
161
162
return true;
163
}
164
165
// Reference: https://en.wikipedia.org/wiki/Filename
166
const WINDOWS_INVALID_FILE_CHARS = /[\\/:\*\?"<>\|]/g;
167
const UNIX_INVALID_FILE_CHARS = /[/]/g;
168
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])(\.(.*?))?$/i;
169
export function isValidBasename(name: string | null | undefined, isWindowsOS: boolean = isWindows): boolean {
170
const invalidFileChars = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS;
171
172
if (!name || name.length === 0 || /^\s+$/.test(name)) {
173
return false; // require a name that is not just whitespace
174
}
175
176
invalidFileChars.lastIndex = 0; // the holy grail of software development
177
if (invalidFileChars.test(name)) {
178
return false; // check for certain invalid file characters
179
}
180
181
if (isWindowsOS && WINDOWS_FORBIDDEN_NAMES.test(name)) {
182
return false; // check for certain invalid file names
183
}
184
185
if (name === '.' || name === '..') {
186
return false; // check for reserved values
187
}
188
189
if (isWindowsOS && name[name.length - 1] === '.') {
190
return false; // Windows: file cannot end with a "."
191
}
192
193
if (isWindowsOS && name.length !== name.trim().length) {
194
return false; // Windows: file cannot end with a whitespace
195
}
196
197
if (name.length > 255) {
198
return false; // most file systems do not allow files > 255 length
199
}
200
201
return true;
202
}
203
204
/**
205
* @deprecated please use `IUriIdentityService.extUri.isEqual` instead. If you are
206
* in a context without services, consider to pass down the `extUri` from the outside
207
* or use `extUriBiasedIgnorePathCase` if you know what you are doing.
208
*/
209
export function isEqual(pathA: string, pathB: string, ignoreCase?: boolean): boolean {
210
const identityEquals = (pathA === pathB);
211
if (!ignoreCase || identityEquals) {
212
return identityEquals;
213
}
214
215
if (!pathA || !pathB) {
216
return false;
217
}
218
219
return equalsIgnoreCase(pathA, pathB);
220
}
221
222
/**
223
* @deprecated please use `IUriIdentityService.extUri.isEqualOrParent` instead. If
224
* you are in a context without services, consider to pass down the `extUri` from the
225
* outside, or use `extUriBiasedIgnorePathCase` if you know what you are doing.
226
*/
227
export function isEqualOrParent(base: string, parentCandidate: string, ignoreCase?: boolean, separator = sep): boolean {
228
if (base === parentCandidate) {
229
return true;
230
}
231
232
if (!base || !parentCandidate) {
233
return false;
234
}
235
236
if (parentCandidate.length > base.length) {
237
return false;
238
}
239
240
if (ignoreCase) {
241
const beginsWith = startsWithIgnoreCase(base, parentCandidate);
242
if (!beginsWith) {
243
return false;
244
}
245
246
if (parentCandidate.length === base.length) {
247
return true; // same path, different casing
248
}
249
250
let sepOffset = parentCandidate.length;
251
if (parentCandidate.charAt(parentCandidate.length - 1) === separator) {
252
sepOffset--; // adjust the expected sep offset in case our candidate already ends in separator character
253
}
254
255
return base.charAt(sepOffset) === separator;
256
}
257
258
if (parentCandidate.charAt(parentCandidate.length - 1) !== separator) {
259
parentCandidate += separator;
260
}
261
262
return base.indexOf(parentCandidate) === 0;
263
}
264
265
export function isWindowsDriveLetter(char0: number): boolean {
266
return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z;
267
}
268
269
export function sanitizeFilePath(candidate: string, cwd: string): string {
270
271
// Special case: allow to open a drive letter without trailing backslash
272
if (isWindows && candidate.endsWith(':')) {
273
candidate += sep;
274
}
275
276
// Ensure absolute
277
if (!isAbsolute(candidate)) {
278
candidate = join(cwd, candidate);
279
}
280
281
// Ensure normalized
282
candidate = normalize(candidate);
283
284
// Ensure no trailing slash/backslash
285
return removeTrailingPathSeparator(candidate);
286
}
287
288
export function removeTrailingPathSeparator(candidate: string): string {
289
if (isWindows) {
290
candidate = rtrim(candidate, sep);
291
292
// Special case: allow to open drive root ('C:\')
293
if (candidate.endsWith(':')) {
294
candidate += sep;
295
}
296
297
} else {
298
candidate = rtrim(candidate, sep);
299
300
// Special case: allow to open root ('/')
301
if (!candidate) {
302
candidate = sep;
303
}
304
}
305
306
return candidate;
307
}
308
309
export function isRootOrDriveLetter(path: string): boolean {
310
const pathNormalized = normalize(path);
311
312
if (isWindows) {
313
if (path.length > 3) {
314
return false;
315
}
316
317
return hasDriveLetter(pathNormalized) &&
318
(path.length === 2 || pathNormalized.charCodeAt(2) === CharCode.Backslash);
319
}
320
321
return pathNormalized === posix.sep;
322
}
323
324
export function hasDriveLetter(path: string, isWindowsOS: boolean = isWindows): boolean {
325
if (isWindowsOS) {
326
return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === CharCode.Colon;
327
}
328
329
return false;
330
}
331
332
export function getDriveLetter(path: string, isWindowsOS: boolean = isWindows): string | undefined {
333
return hasDriveLetter(path, isWindowsOS) ? path[0] : undefined;
334
}
335
336
export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number {
337
if (candidate.length > path.length) {
338
return -1;
339
}
340
341
if (path === candidate) {
342
return 0;
343
}
344
345
if (ignoreCase) {
346
path = path.toLowerCase();
347
candidate = candidate.toLowerCase();
348
}
349
350
return path.indexOf(candidate);
351
}
352
353
export interface IPathWithLineAndColumn {
354
path: string;
355
line?: number;
356
column?: number;
357
}
358
359
export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
360
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
361
362
let path: string | undefined = undefined;
363
let line: number | undefined = undefined;
364
let column: number | undefined = undefined;
365
366
for (const segment of segments) {
367
const segmentAsNumber = Number(segment);
368
if (!isNumber(segmentAsNumber)) {
369
path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
370
} else if (line === undefined) {
371
line = segmentAsNumber;
372
} else if (column === undefined) {
373
column = segmentAsNumber;
374
}
375
}
376
377
if (!path) {
378
throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`');
379
}
380
381
return {
382
path,
383
line: line !== undefined ? line : undefined,
384
column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set
385
};
386
}
387
388
const pathChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
389
const windowsSafePathFirstChars = 'BDEFGHIJKMOQRSTUVWXYZbdefghijkmoqrstuvwxyz0123456789';
390
391
export function randomPath(parent?: string, prefix?: string, randomLength = 8): string {
392
let suffix = '';
393
for (let i = 0; i < randomLength; i++) {
394
let pathCharsTouse: string;
395
if (i === 0 && isWindows && !prefix && (randomLength === 3 || randomLength === 4)) {
396
397
// Windows has certain reserved file names that cannot be used, such
398
// as AUX, CON, PRN, etc. We want to avoid generating a random name
399
// that matches that pattern, so we use a different set of characters
400
// for the first character of the name that does not include any of
401
// the reserved names first characters.
402
403
pathCharsTouse = windowsSafePathFirstChars;
404
} else {
405
pathCharsTouse = pathChars;
406
}
407
408
suffix += pathCharsTouse.charAt(Math.floor(Math.random() * pathCharsTouse.length));
409
}
410
411
let randomFileName: string;
412
if (prefix) {
413
randomFileName = `${prefix}-${suffix}`;
414
} else {
415
randomFileName = suffix;
416
}
417
418
if (parent) {
419
return join(parent, randomFileName);
420
}
421
422
return randomFileName;
423
}
424
425