uri.ts 18.4 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { isWindows } from 'vs/base/common/platform';
7
import { CharCode } from 'vs/base/common/charCode';
8

9 10 11
const _schemePattern = /^\w[\w\d+.-]*$/;
const _singleSlashStart = /^\//;
const _doubleSlashStart = /^\/\//;
12

13
function _validateUri(ret: URI): void {
14

15 16
	// scheme, must be set
	if (!ret.scheme) {
17
		throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`);
18
	}
19

20 21
	// scheme, https://tools.ietf.org/html/rfc3986#section-3.1
	// ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
22
	if (ret.scheme && !_schemePattern.test(ret.scheme)) {
23 24 25 26 27 28 29 30 31 32
		throw new Error('[UriError]: Scheme contains illegal characters.');
	}

	// 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) {
		if (ret.authority) {
33
			if (!_singleSlashStart.test(ret.path)) {
34 35 36
				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');
			}
		} else {
37
			if (_doubleSlashStart.test(ret.path)) {
38 39 40 41 42 43
				throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")');
			}
		}
	}
}

44
// graceful behaviour when scheme is missing: fallback to using 'file'-scheme
45
function _schemeFix(scheme: string): string {
46
	if (!scheme) {
47 48
		console.trace('BAD uri lacks scheme, falling back to file-scheme.');
		scheme = 'file';
49 50 51 52
	}
	return scheme;
}

53 54 55 56 57 58 59 60 61 62 63 64
// implements a bit of https://tools.ietf.org/html/rfc3986#section-5
function _referenceResolution(scheme: string, path: string): string {

	// 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':
			if (!path) {
65 66 67
				path = _slash;
			} else if (path[0] !== _slash) {
				path = _slash + path;
68 69 70 71 72 73
			}
			break;
	}
	return path;
}

74 75 76
const _empty = '';
const _slash = '/';
const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
77

E
Erich Gamma 已提交
78 79
/**
 * Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
80
 * This class is a simple parser which creates the basic component parts
E
Erich Gamma 已提交
81 82 83
 * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
 * and encoding.
 *
84
 *       foo://example.com:8042/over/there?name=ferret#nose
E
Erich Gamma 已提交
85 86 87 88 89 90 91
 *       \_/   \______________/\_________/ \_________/ \__/
 *        |           |            |            |        |
 *     scheme     authority       path        query   fragment
 *        |   _____________________|__
 *       / \ /                        \
 *       urn:example:animal:ferret:nose
 */
92
export class URI implements UriComponents {
E
Erich Gamma 已提交
93

94 95 96 97 98 99 100 101 102 103 104
	static isUri(thing: any): thing is URI {
		if (thing instanceof URI) {
			return true;
		}
		if (!thing) {
			return false;
		}
		return typeof (<URI>thing).authority === 'string'
			&& typeof (<URI>thing).fragment === 'string'
			&& typeof (<URI>thing).path === 'string'
			&& typeof (<URI>thing).query === 'string'
J
Johannes Rieken 已提交
105 106 107 108
			&& typeof (<URI>thing).scheme === 'string'
			&& typeof (<URI>thing).fsPath === 'function'
			&& typeof (<URI>thing).with === 'function'
			&& typeof (<URI>thing).toString === 'function';
109 110
	}

E
Erich Gamma 已提交
111 112 113 114
	/**
	 * scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'.
	 * The part before the first colon.
	 */
J
Johannes Rieken 已提交
115
	readonly scheme: string;
E
Erich Gamma 已提交
116 117 118 119 120

	/**
	 * authority is the 'www.msft.com' part of 'http://www.msft.com/some/path?query#fragment'.
	 * The part between the first double slashes and the next slash.
	 */
J
Johannes Rieken 已提交
121
	readonly authority: string;
E
Erich Gamma 已提交
122 123 124 125

	/**
	 * path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.
	 */
J
Johannes Rieken 已提交
126
	readonly path: string;
E
Erich Gamma 已提交
127 128 129 130

	/**
	 * query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'.
	 */
J
Johannes Rieken 已提交
131
	readonly query: string;
E
Erich Gamma 已提交
132 133 134 135

	/**
	 * fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'.
	 */
J
Johannes Rieken 已提交
136 137 138 139 140
	readonly fragment: string;

	/**
	 * @internal
	 */
141
	protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string);
J
Johannes Rieken 已提交
142

J
Johannes Rieken 已提交
143 144 145 146
	/**
	 * @internal
	 */
	protected constructor(components: UriComponents);
J
Johannes Rieken 已提交
147

J
Johannes Rieken 已提交
148 149 150
	/**
	 * @internal
	 */
151
	protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string) {
J
Johannes Rieken 已提交
152 153

		if (typeof schemeOrData === 'object') {
154 155 156 157 158
			this.scheme = schemeOrData.scheme || _empty;
			this.authority = schemeOrData.authority || _empty;
			this.path = schemeOrData.path || _empty;
			this.query = schemeOrData.query || _empty;
			this.fragment = schemeOrData.fragment || _empty;
J
Johannes Rieken 已提交
159 160 161 162
			// no validation because it's this URI
			// that creates uri components.
			// _validateUri(this);
		} else {
163
			this.scheme = _schemeFix(schemeOrData);
164 165 166 167
			this.authority = authority || _empty;
			this.path = _referenceResolution(this.scheme, path || _empty);
			this.query = query || _empty;
			this.fragment = fragment || _empty;
168

169
			_validateUri(this);
J
Johannes Rieken 已提交
170
		}
E
Erich Gamma 已提交
171 172 173 174 175
	}

	// ---- filesystem path -----------------------

	/**
P
Pascal Borreli 已提交
176
	 * Returns a string representing the corresponding file system path of this URI.
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
	 * 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).
E
Erich Gamma 已提交
198
	 */
J
Johannes Rieken 已提交
199
	get fsPath(): string {
200 201 202 203
		// if (this.scheme !== 'file') {
		// 	console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`);
		// }
		return _makeFsPath(this);
E
Erich Gamma 已提交
204 205 206 207
	}

	// ---- modify to new -------------------------

208
	with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null; }): URI {
209 210 211 212 213

		if (!change) {
			return this;
		}

214
		let { scheme, authority, path, query, fragment } = change;
R
Rob Lourens 已提交
215
		if (scheme === undefined) {
216
			scheme = this.scheme;
J
Johannes Rieken 已提交
217
		} else if (scheme === null) {
218
			scheme = _empty;
219
		}
R
Rob Lourens 已提交
220
		if (authority === undefined) {
221
			authority = this.authority;
J
Johannes Rieken 已提交
222
		} else if (authority === null) {
223
			authority = _empty;
224
		}
R
Rob Lourens 已提交
225
		if (path === undefined) {
226
			path = this.path;
J
Johannes Rieken 已提交
227
		} else if (path === null) {
228
			path = _empty;
229
		}
R
Rob Lourens 已提交
230
		if (query === undefined) {
231
			query = this.query;
J
Johannes Rieken 已提交
232
		} else if (query === null) {
233
			query = _empty;
234
		}
R
Rob Lourens 已提交
235
		if (fragment === undefined) {
236
			fragment = this.fragment;
J
Johannes Rieken 已提交
237
		} else if (fragment === null) {
238
			fragment = _empty;
239
		}
240 241 242 243 244 245 246 247 248 249

		if (scheme === this.scheme
			&& authority === this.authority
			&& path === this.path
			&& query === this.query
			&& fragment === this.fragment) {

			return this;
		}

250
		return new _URI(scheme, authority, path, query, fragment);
E
Erich Gamma 已提交
251 252 253 254
	}

	// ---- parse & validate ------------------------

255 256 257 258 259 260
	/**
	 * Creates a new URI from a string, e.g. `http://www.msft.com/some/path`,
	 * `file:///usr/home`, or `scheme:with/path`.
	 *
	 * @param value A string which represents an URI (see `URI#toString`).
	 */
261 262
	static parse(value: string): URI {
		const match = _regexp.exec(value);
J
Johannes Rieken 已提交
263
		if (!match) {
264
			return new _URI(_empty, _empty, _empty, _empty, _empty);
J
Johannes Rieken 已提交
265
		}
266 267 268 269 270 271
		return new _URI(
			match[2] || _empty,
			decodeURIComponent(match[4] || _empty),
			decodeURIComponent(match[5] || _empty),
			decodeURIComponent(match[7] || _empty),
			decodeURIComponent(match[9] || _empty)
J
Johannes Rieken 已提交
272
		);
E
Erich Gamma 已提交
273 274
	}

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
	/**
	 * 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`)
	 */
J
Johannes Rieken 已提交
296
	static file(path: string): URI {
E
Erich Gamma 已提交
297

298
		let authority = _empty;
299

300
		// normalize to fwd-slashes on windows,
A
typo  
Andre Weinand 已提交
301
		// on other systems bwd-slashes are valid
J
Johannes Rieken 已提交
302
		// filename character, eg /f\oo/ba\r.txt
303
		if (isWindows) {
304
			path = path.replace(/\\/g, _slash);
305
		}
306 307 308

		// check for authority as used in UNC shares
		// or use the path as given
309 310
		if (path[0] === _slash && path[1] === _slash) {
			const idx = path.indexOf(_slash, 2);
311
			if (idx === -1) {
J
Johannes Rieken 已提交
312
				authority = path.substring(2);
313
				path = _slash;
314
			} else {
J
Johannes Rieken 已提交
315
				authority = path.substring(2, idx);
316
				path = path.substring(idx) || _slash;
317 318 319
			}
		}

320
		return new _URI('file', authority, path, _empty, _empty);
E
Erich Gamma 已提交
321 322
	}

323
	static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string; }): URI {
324
		return new _URI(
J
Johannes Rieken 已提交
325 326 327 328 329 330
			components.scheme,
			components.authority,
			components.path,
			components.query,
			components.fragment,
		);
E
Erich Gamma 已提交
331 332 333 334
	}

	// ---- printing/externalize ---------------------------

335
	/**
A
Anuj 已提交
336
	 * Creates a string representation for this URI. It's guaranteed that calling
337 338 339 340 341 342
	 * `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.
343
	 *
344
	 * @param skipEncoding Do not encode the result, default is `false`
345
	 */
J
Johannes Rieken 已提交
346
	toString(skipEncoding: boolean = false): string {
347
		return _asFormatted(this, skipEncoding);
348 349
	}

J
Johannes Rieken 已提交
350
	toJSON(): UriComponents {
351
		return this;
352 353
	}

J
Johannes Rieken 已提交
354
	static revive(data: UriComponents | URI): URI;
355 356 357 358
	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 {
J
Johannes Rieken 已提交
359
		if (!data) {
360
			return data;
J
Johannes Rieken 已提交
361 362 363
		} else if (data instanceof URI) {
			return data;
		} else {
364
			const result = new _URI(data);
365
			result._formatted = (<UriState>data).external;
366
			result._fsPath = (<UriState>data)._sep === _pathSepMarker ? (<UriState>data).fsPath : null;
J
Johannes Rieken 已提交
367 368
			return result;
		}
369 370 371
	}
}

J
Johannes Rieken 已提交
372
export interface UriComponents {
373 374 375 376 377 378 379 380
	scheme: string;
	authority: string;
	path: string;
	query: string;
	fragment: string;
}

interface UriState extends UriComponents {
381
	$mid: number;
382
	external: string;
J
Johannes Rieken 已提交
383
	fsPath: string;
384
	_sep: 1 | undefined;
385 386
}

387
const _pathSepMarker = isWindows ? 1 : undefined;
388

389 390
// tslint:disable-next-line:class-name
class _URI extends URI {
391

392
	_formatted: string | null = null;
393
	_fsPath: string | null = null;
394 395 396

	get fsPath(): string {
		if (!this._fsPath) {
397
			this._fsPath = _makeFsPath(this);
398 399 400 401
		}
		return this._fsPath;
	}

J
Johannes Rieken 已提交
402
	toString(skipEncoding: boolean = false): string {
403 404 405 406 407 408
		if (!skipEncoding) {
			if (!this._formatted) {
				this._formatted = _asFormatted(this, false);
			}
			return this._formatted;
		} else {
409
			// we don't cache that
410
			return _asFormatted(this, true);
J
Johannes Rieken 已提交
411 412
		}
	}
413

J
Johannes Rieken 已提交
414
	toJSON(): UriComponents {
415
		const res = <UriState>{
416
			$mid: 1
417 418 419 420
		};
		// cached state
		if (this._fsPath) {
			res.fsPath = this._fsPath;
421
			res._sep = _pathSepMarker;
422
		}
423 424
		if (this._formatted) {
			res.external = this._formatted;
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
		}
		// uri components
		if (this.path) {
			res.path = this.path;
		}
		if (this.scheme) {
			res.scheme = this.scheme;
		}
		if (this.authority) {
			res.authority = this.authority;
		}
		if (this.query) {
			res.query = this.query;
		}
		if (this.fragment) {
			res.fragment = this.fragment;
		}
		return res;
	}
444 445
}

446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
// 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, allowSlash: boolean): string {
	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.A && code <= CharCode.Z)
			|| (code >= CharCode.Digit0 && code <= CharCode.Digit9)
			|| code === CharCode.Dash
			|| code === CharCode.Period
			|| code === CharCode.Underline
			|| code === CharCode.Tilde
			|| (allowSlash && code === CharCode.Slash)
		) {
			// check if we are delaying native encode
			if (nativeEncodePos !== -1) {
				res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos));
				nativeEncodePos = -1;
			}
			// check if we write into a new string (by default we try to return the param)
			if (res !== undefined) {
				res += uriComponent.charAt(pos);
			}
498

499 500 501 502 503
		} else {
			// encoding needed, we need to allocate a new string
			if (res === undefined) {
				res = uriComponent.substr(0, pos);
			}
504

505 506 507
			// check with default table first
			const escaped = encodeTable[code];
			if (escaped !== undefined) {
J
Johannes Rieken 已提交
508

509 510 511 512 513
				// check if we are delaying native encode
				if (nativeEncodePos !== -1) {
					res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos));
					nativeEncodePos = -1;
				}
J
Johannes Rieken 已提交
514

515 516 517 518 519 520 521
				// append escaped variant to result
				res += escaped;

			} else if (nativeEncodePos === -1) {
				// use native encode only when needed
				nativeEncodePos = pos;
			}
J
Johannes Rieken 已提交
522 523 524
		}
	}

525 526
	if (nativeEncodePos !== -1) {
		res += encodeURIComponent(uriComponent.substring(nativeEncodePos));
J
Johannes Rieken 已提交
527 528
	}

529
	return res !== undefined ? res : uriComponent;
530 531
}

532 533 534 535 536 537 538
function encodeURIComponentMinimal(path: string): string {
	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) {
			if (res === undefined) {
				res = path.substr(0, pos);
J
Johannes Rieken 已提交
539
			}
540 541 542 543
			res += encodeTable[code];
		} else {
			if (res !== undefined) {
				res += path[pos];
J
Johannes Rieken 已提交
544 545 546
			}
		}
	}
547
	return res !== undefined ? res : path;
J
Johannes Rieken 已提交
548 549
}

550 551 552 553
/**
 * Compute `fsPath` for the given uri
 */
function _makeFsPath(uri: URI): string {
554

555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
	let value: string;
	if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') {
		// unc path: file://shares/c$/far/boo
		value = `//${uri.authority}${uri.path}`;
	} else if (
		uri.path.charCodeAt(0) === CharCode.Slash
		&& (uri.path.charCodeAt(1) >= CharCode.A && uri.path.charCodeAt(1) <= CharCode.Z || uri.path.charCodeAt(1) >= CharCode.a && uri.path.charCodeAt(1) <= CharCode.z)
		&& uri.path.charCodeAt(2) === CharCode.Colon
	) {
		// windows drive letter: file:///c:/far/boo
		value = uri.path[1].toLowerCase() + uri.path.substr(2);
	} else {
		// other path
		value = uri.path;
	}
	if (isWindows) {
		value = value.replace(/\//g, '\\');
	}
	return value;
}
575

576 577 578
/**
 * Create the external version of a uri
 */
579 580 581 582 583
function _asFormatted(uri: URI, skipEncoding: boolean): string {

	const encoder = !skipEncoding
		? encodeURIComponentFast
		: encodeURIComponentMinimal;
E
Erich Gamma 已提交
584

585
	let res = '';
586
	let { scheme, authority, path, query, fragment } = uri;
587
	if (scheme) {
588 589
		res += scheme;
		res += ':';
590 591
	}
	if (authority || scheme === 'file') {
592 593
		res += _slash;
		res += _slash;
594 595
	}
	if (authority) {
596 597 598 599 600 601 602 603
		let idx = authority.indexOf('@');
		if (idx !== -1) {
			// <user>@<auth>
			const userinfo = authority.substr(0, idx);
			authority = authority.substr(idx + 1);
			idx = userinfo.indexOf(':');
			if (idx === -1) {
				res += encoder(userinfo, false);
J
Johannes Rieken 已提交
604
			} else {
605 606 607 608
				// <user>:<pass>@<auth>
				res += encoder(userinfo.substr(0, idx), false);
				res += ':';
				res += encoder(userinfo.substr(idx + 1), false);
E
Erich Gamma 已提交
609
			}
610
			res += '@';
J
Johannes Rieken 已提交
611
		}
612 613 614 615 616 617 618 619
		authority = authority.toLowerCase();
		idx = authority.indexOf(':');
		if (idx === -1) {
			res += encoder(authority, false);
		} else {
			// <auth>:<port>
			res += encoder(authority.substr(0, idx), false);
			res += authority.substr(idx);
J
Johannes Rieken 已提交
620
		}
621 622
	}
	if (path) {
623 624 625 626 627 628 629 630 631 632 633
		// lower-case windows drive letters in /C:/fff or C:/fff
		if (path.length >= 3 && path.charCodeAt(0) === CharCode.Slash && path.charCodeAt(2) === CharCode.Colon) {
			const code = path.charCodeAt(1);
			if (code >= CharCode.A && code <= CharCode.Z) {
				path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3
			}
		} else if (path.length >= 2 && path.charCodeAt(1) === CharCode.Colon) {
			const code = path.charCodeAt(0);
			if (code >= CharCode.A && code <= CharCode.Z) {
				path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "/c:".length === 3
			}
634
		}
635 636
		// encode the rest of the path
		res += encoder(path, true);
637 638
	}
	if (query) {
639
		res += '?';
640
		res += encoder(query, false);
641 642
	}
	if (fragment) {
643
		res += '#';
644
		res += !skipEncoding ? encodeURIComponentFast(fragment, false) : fragment;
E
Erich Gamma 已提交
645
	}
646
	return res;
E
Erich Gamma 已提交
647
}