src/vs/base/common/uri.ts
754 LOC · 729 covered · 25 uncovered · 202 ranges · 20961 concepts · 88 introducers · 12741 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
map.ts ×97
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CharCode } from './charCode.js';
import { MarshalledId } from './marshallingIds.js';
import * as paths from './path.js';
import { isWindows } from './platform.js';
const _schemePattern = /^\w[\w\d+.-]*$/;
const _singleSlashStart = /^\//;
const _doubleSlashStart = /^\/\//;
// scheme, must be set
if (!ret.scheme && _strict) {
throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`);
uri.ts ×1
}
// scheme, https://tools.ietf.org/html/rfc3986#section-3.1
// ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
const detail = matches.length > 0
? ` Found '${matches[0][0]}' at index ${matches[0].index} (${matches.length} total)`
: '';
throw new Error(`[UriError]: Scheme contains illegal characters.${detail} (len:${ret.scheme.length})`);
uri.ts ×2
}
// path, http://tools.ietf.org/html/rfc3986#section-3.3
// If a URI contains an authority component, then the path component
// must either be empty or begin with a slash ("/") character. If a URI
// does not contain an authority component, then the path cannot begin
// with two slash characters ("//").
if (ret.path) {
throw new Error('[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character');
uri.ts ×1
}
throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")');
uri.ts ×1
}
// for a while we allowed uris *without* schemes and this is the migration
// for them, e.g. an uri without scheme and without strict-mode warns and falls
// back to the file-scheme. that should cause the least carnage and still be a
// clear warning
if (!scheme && !_strict) {
}
}
// implements a bit of https://tools.ietf.org/html/rfc3986#section-5
// the slash-character is our 'default base' as we don't
// support constructing URIs relative to other URIs. This
// also means that we alter and potentially break paths.
// see https://tools.ietf.org/html/rfc3986#section-5.1.4
switch (scheme) {
case 'https':
case 'http':
case 'file':
}
return path;
}
const _empty = '';
const _slash = '/';
const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
/**
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
* This class is a simple parser which creates the basic component parts
* (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
* and encoding.
*
* ```txt
* foo://example.com:8042/over/there?name=ferret#nose
* \_/ \______________/\_________/ \_________/ \__/
* | | | | |
* scheme authority path query fragment
* | _____________________|__
* / \ / \
* urn:example:animal:ferret:nose
* ```
*/
export class URI implements UriComponents {
static isUri(thing: unknown): thing is URI {
}
}
&& typeof (<URI>thing).path === 'string'
&& typeof (<URI>thing).query === 'string'
&& typeof (<URI>thing).scheme === 'string'
&& typeof (<URI>thing).fsPath === 'string'
&& typeof (<URI>thing).with === 'function'
&& typeof (<URI>thing).toString === 'function';
/**
* scheme is the 'http' part of 'http://www.example.com/some/path?query#fragment'.
* The part before the first colon.
*/
readonly scheme: string;
/**
* authority is the 'www.example.com' part of 'http://www.example.com/some/path?query#fragment'.
* The part between the first double slashes and the next slash.
*/
readonly authority: string;
/**
* path is the '/some/path' part of 'http://www.example.com/some/path?query#fragment'.
*/
readonly path: string;
/**
* query is the 'query' part of 'http://www.example.com/some/path?query#fragment'.
*/
readonly query: string;
/**
* fragment is the 'fragment' part of 'http://www.example.com/some/path?query#fragment'.
*/
readonly fragment: string;
/**
* @internal
*/
protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean);
/**
* @internal
*/
protected constructor(components: UriComponents);
/**
* @internal
*/
protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) {
if (typeof schemeOrData === 'object') {
this.authority = schemeOrData.authority || _empty;
this.path = schemeOrData.path || _empty;
this.query = schemeOrData.query || _empty;
this.fragment = schemeOrData.fragment || _empty;
// no validation because it's this URI
// that creates uri components.
// _validateUri(this);
this.scheme = _schemeFix(schemeOrData, _strict);
this.authority = authority || _empty;
this.path = _referenceResolution(this.scheme, path || _empty);
this.query = query || _empty;
this.fragment = fragment || _empty;
_validateUri(this, _strict);
}
}
// ---- filesystem path -----------------------
/**
* Returns a string representing the corresponding file system path of this URI.
* Will handle UNC paths, normalizes windows drive letters to lower-case, and uses the
* platform specific path separator.
*
* * Will *not* validate the path for invalid characters and semantics.
* * Will *not* look at the scheme of this URI.
* * The result shall *not* be used for display purposes but for accessing a file on disk.
*
*
* The *difference* to `URI#path` is the use of the platform specific separator and the handling
* of UNC paths. See the below sample of a file-uri with an authority (UNC path).
*
* ```ts
const u = URI.parse('file://server/c$/folder/file.txt')
u.authority === 'server'
u.path === '/shares/c$/file.txt'
u.fsPath === '\\server\c$\folder\file.txt'
```
*
* Using `URI#path` to read a file (using fs-apis) would not be enough because parts of the path,
* namely the server name, would be missing. Therefore `URI#fsPath` exists - it's sugar to ease working
* with URIs that represent files on disk (`file` scheme).
*/
get fsPath(): string {
// if (this.scheme !== 'file') {
// console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`);
// }
return uriToFsPath(this, false);
}
// ---- modify to new -------------------------
with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null }): URI {
if (!change) {
}
let { scheme, authority, path, query, fragment } = change;
if (scheme === undefined) {
scheme = _empty;
}
}
}
}
}
if (scheme === this.scheme
&& path === this.path
return this;
}
return new Uri(scheme, authority, path, query, fragment);
// ---- parse & validate ------------------------
/**
* Creates a new URI from a string, e.g. `http://www.example.com/some/path`,
* `file:///usr/home`, or `scheme:with/path`.
*
* @param value A string which represents an URI (see `URI#toString`).
*/
static parse(value: string, _strict: boolean = false): URI {
if (!match) {
return new Uri(_empty, _empty, _empty, _empty, _empty);
}
match[2] || _empty,
percentDecode(match[4] || _empty),
percentDecode(match[5] || _empty),
percentDecode(match[7] || _empty),
percentDecode(match[9] || _empty),
_strict
);
}
/**
* Creates a new URI from a file system path, e.g. `c:\my\files`,
* `/usr/home`, or `\\server\share\some\path`.
*
* The *difference* between `URI#parse` and `URI#file` is that the latter treats the argument
* as path, not as stringified-uri. E.g. `URI.file(path)` is **not the same as**
* `URI.parse('file://' + path)` because the path might contain characters that are
* interpreted (# and ?). See the following sample:
* ```ts
const good = URI.file('/coding/c#/project1');
good.scheme === 'file';
good.path === '/coding/c#/project1';
good.fragment === '';
const bad = URI.parse('file://' + '/coding/c#/project1');
bad.scheme === 'file';
bad.path === '/coding/c'; // path is now broken
bad.fragment === '/project1';
```
*
* @param path A file system path (see `URI#fsPath`)
*/
static file(path: string): URI {
let authority = _empty;
// normalize to fwd-slashes on windows,
// on other systems bwd-slashes are valid
// filename character, eg /f\oo/ba\r.txt
if (isWindows) {
path = path.replace(/\\/g, _slash);
}
// check for authority as used in UNC shares
// or use the path as given
if (path[0] === _slash && path[1] === _slash) {
if (idx === -1) {
authority = path.substring(2);
path = _slash;
authority = path.substring(2, idx);
path = path.substring(idx) || _slash;
}
}
return new Uri('file', authority, path, _empty, _empty);
}
/**
* Creates new URI from uri components.
*
* Unless `strict` is `true` the scheme is defaults to be `file`. This function performs
* validation and should be used for untrusted uri components retrieved from storage,
* user input, command arguments etc
*/
static from(components: UriComponents, strict?: boolean): URI {
components.scheme,
components.authority,
components.path,
components.query,
components.fragment,
strict
);
return result;
}
/**
* Join a URI path with path fragments and normalizes the resulting path.
*
* @param uri The input URI.
* @param pathFragment The path fragment to add to the URI path.
* @returns The resulting URI.
*/
static joinPath(uri: URI, ...pathFragment: string[]): URI {
throw new Error(`[UriError]: cannot call joinPath on URI without path: ${uri.toString()}`);
uri.ts ×1
}
if (isWindows && uri.scheme === 'file') {
newPath = URI.file(paths.win32.join(uriToFsPath(uri, true), ...pathFragment)).path;
newPath = paths.posix.join(uri.path, ...pathFragment);
}
return uri.with({ path: newPath });
}
// ---- printing/externalize ---------------------------
/**
* Creates a string representation for this URI. It's guaranteed that calling
* `URI.parse` with the result of this function creates an URI which is equal
* to this URI.
*
* * The result shall *not* be used for display purposes but for externalization or transport.
* * The result will be encoded using the percentage encoding and encoding happens mostly
* ignore the scheme-specific encoding rules.
*
* @param skipEncoding Do not encode the result, default is `false`
*/
toString(skipEncoding: boolean = false): string {
return _asFormatted(this, skipEncoding);
}
toJSON(): UriComponents {
return this;
}
/**
* A helper function to revive URIs.
*
* **Note** that this function should only be used when receiving URI#toJSON generated data
* and that it doesn't do any validation. Use {@link URI.from} when received "untrusted"
* uri components such as command arguments or data from storage.
*
* @param data The URI components or URI to revive.
* @returns The revived URI or undefined or null.
*/
static revive(data: UriComponents | URI): URI;
static revive(data: UriComponents | URI | undefined): URI | undefined;
static revive(data: UriComponents | URI | null): URI | null;
static revive(data: UriComponents | URI | undefined | null): URI | undefined | null;
static revive(data: UriComponents | URI | undefined | null): URI | undefined | null {
result._formatted = (<UriState>data).external ?? null;
result._fsPath = (<UriState>data)._sep === _pathSepMarker ? (<UriState>data).fsPath ?? null : null;
return result;
}
[Symbol.for('debug.description')]() {
}
export interface UriComponents {
scheme: string;
authority?: string;
path?: string;
query?: string;
fragment?: string;
}
export function isUriComponents(thing: unknown): thing is UriComponents {
}
&& (typeof (<UriComponents>thing).authority === 'string' || typeof (<UriComponents>thing).authority === 'undefined')
uri.ts ×1
&& (typeof (<UriComponents>thing).path === 'string' || typeof (<UriComponents>thing).path === 'undefined')
&& (typeof (<UriComponents>thing).query === 'string' || typeof (<UriComponents>thing).query === 'undefined')
&& (typeof (<UriComponents>thing).fragment === 'string' || typeof (<UriComponents>thing).fragment === 'undefined');
interface UriState extends UriComponents {
$mid: MarshalledId.Uri;
external?: string;
fsPath?: string;
_sep?: 1;
}
const _pathSepMarker = isWindows ? 1 : undefined;
// This class exists so that URI is compatible with vscode.Uri (API).
_formatted: string | null = null;
_fsPath: string | null = null;
override get fsPath(): string {
this._fsPath = uriToFsPath(this, false);
}
return this._fsPath;
}
override toString(skipEncoding: boolean = false): string {
this._formatted = _asFormatted(this, false);
}
return this._formatted;
return _asFormatted(this, true);
}
override toJSON(): UriComponents {
const res = <UriState>{
$mid: MarshalledId.Uri
};
// cached state
if (this._fsPath) {
res.fsPath = this._fsPath;
res._sep = _pathSepMarker;
}
}
if (this.path) {
res.path = this.path;
}
// TODO
// this isn't correct and can violate the UriComponents contract but
// this is part of the vscode.Uri API and we shouldn't change how that
// works anymore
if (this.scheme) {
res.scheme = this.scheme;
}
if (this.authority) {
}
}
}
}
// reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2
const encodeTable: { [ch: number]: string } = {
[CharCode.Colon]: '%3A', // gen-delims
[CharCode.Slash]: '%2F',
[CharCode.QuestionMark]: '%3F',
[CharCode.Hash]: '%23',
[CharCode.OpenSquareBracket]: '%5B',
[CharCode.CloseSquareBracket]: '%5D',
[CharCode.AtSign]: '%40',
[CharCode.ExclamationMark]: '%21', // sub-delims
[CharCode.DollarSign]: '%24',
[CharCode.Ampersand]: '%26',
[CharCode.SingleQuote]: '%27',
[CharCode.OpenParen]: '%28',
[CharCode.CloseParen]: '%29',
[CharCode.Asterisk]: '%2A',
[CharCode.Plus]: '%2B',
[CharCode.Comma]: '%2C',
[CharCode.Semicolon]: '%3B',
[CharCode.Equals]: '%3D',
[CharCode.Space]: '%20',
};
function encodeURIComponentFast(uriComponent: string, isPath: boolean, isAuthority: boolean): string {
uri.ts ×8
let res: string | undefined = undefined;
let nativeEncodePos = -1;
for (let pos = 0; pos < uriComponent.length; pos++) {
const code = uriComponent.charCodeAt(pos);
// unreserved characters: https://tools.ietf.org/html/rfc3986#section-2.3
if (
(code >= CharCode.a && code <= CharCode.z)
|| (code >= CharCode.Digit0 && code <= CharCode.Digit9)
|| code === CharCode.Underline
|| code === CharCode.Tilde
|| (isPath && code === CharCode.Slash)
|| (isAuthority && code === CharCode.CloseSquareBracket)
|| (isAuthority && code === CharCode.Colon)
// check if we are delaying native encode
if (nativeEncodePos !== -1) {
nativeEncodePos = -1;
}
if (res !== undefined) {
}
} else {
if (res === undefined) {
res = uriComponent.substr(0, pos);
}
// check with default table first
const escaped = encodeTable[code];
if (escaped !== undefined) {
// check if we are delaying native encode
if (nativeEncodePos !== -1) {
nativeEncodePos = -1;
}
// append escaped variant to result
res += escaped;
nativeEncodePos = pos;
}
if (nativeEncodePos !== -1) {
}
return res !== undefined ? res : uriComponent;
}
let res: string | undefined = undefined;
for (let pos = 0; pos < path.length; pos++) {
const code = path.charCodeAt(pos);
if (code === CharCode.Hash || code === CharCode.QuestionMark) {
res = path.substr(0, pos);
}
res += encodeTable[code];
if (res !== undefined) {
}
}
return res !== undefined ? res : path;
}
/**
* Compute `fsPath` for the given uri
*/
export function uriToFsPath(uri: URI, keepDriveLetterCasing: boolean): string {
let value: string;
if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') {
value = `//${uri.authority}${uri.path}`;
} else if (
&& (uri.path.charCodeAt(1) >= CharCode.A && uri.path.charCodeAt(1) <= CharCode.Z || uri.path.charCodeAt(1) >= CharCode.a && uri.path.charCodeAt(1) <= CharCode.z)
// windows drive letter: file:///c:/far/boo
value = uri.path[1].toLowerCase() + uri.path.substr(2);
} else {
}
value = uri.path;
}
value = value.replace(/\//g, '\\');
}
}
/**
* Create the external version of a uri
*/
const encoder = !skipEncoding
let res = '';
let { scheme, authority, path, query, fragment } = uri;
if (scheme) {
res += scheme;
res += ':';
}
if (authority || scheme === 'file') {
res += _slash;
}
if (idx !== -1) {
const userinfo = authority.substr(0, idx);
authority = authority.substr(idx + 1);
idx = userinfo.lastIndexOf(':');
if (idx === -1) {
res += encoder(userinfo, false, false);
} else {
res += encoder(userinfo.substr(0, idx), false, false);
res += ':';
res += encoder(userinfo.substr(idx + 1), false, true);
}
}
idx = authority.lastIndexOf(':');
if (idx === -1) {
res += encoder(authority.substr(0, idx), false, true);
res += authority.substr(idx);
}
if (path.length >= 3 && path.charCodeAt(0) === CharCode.Slash && path.charCodeAt(2) === CharCode.Colon) {
if (code >= CharCode.A && code <= CharCode.Z) {
path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3
uri.ts ×1
}
if (code >= CharCode.A && code <= CharCode.Z) {
path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "/c:".length === 3
}
}
res += encoder(path, true, false);
}
res += encoder(query, false, false);
}
res += !skipEncoding ? encodeURIComponentFast(fragment, false, false) : fragment;
}
}
// --- decode
try {
return decodeURIComponent(str);
} catch {
return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3));
return str;
}
}
const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
if (!str.match(_rEncodedAsHex)) {
return str;
}
}
/**
* Mapped-type that replaces all occurrences of URI with UriComponents
*/
export type UriDto<T> = { [K in keyof T]: T[K] extends URI
? UriComponents
: UriDto<T[K]> };