uri.ts 19.2 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 12
const _schemePattern = /^\w[\w\d+.-]*$/;
const _singleSlashStart = /^\//;
const _doubleSlashStart = /^\/\//;

13 14 15 16 17 18 19 20 21 22 23
let _throwOnMissingSchema: boolean = true;

/**
 * @internal
 */
export function setUriThrowOnMissingScheme(value: boolean): boolean {
	const old = _throwOnMissingSchema;
	_throwOnMissingSchema = value;
	return old;
}

24
function _validateUri(ret: URI, _strict?: boolean): void {
25

26 27
	// scheme, must be set
	if (!ret.scheme) {
28
		if (_strict || _throwOnMissingSchema) {
29 30 31 32
			throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`);
		} else {
			console.warn(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`);
		}
33
	}
34

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
	// scheme, https://tools.ietf.org/html/rfc3986#section-3.1
	// ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
	if (ret.scheme && !_schemePattern.test(ret.scheme)) {
		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) {
			if (!_singleSlashStart.test(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');
			}
		} else {
			if (_doubleSlashStart.test(ret.path)) {
				throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")');
			}
		}
	}
}

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
// 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
function _schemeFix(scheme: string, _strict: boolean): string {
	if (_strict || _throwOnMissingSchema) {
		return scheme || _empty;
	}
	if (!scheme) {
		console.trace('BAD uri lacks scheme, falling back to file-scheme.');
		scheme = 'file';
	}
	return scheme;
}

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
// 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) {
				path = _slash;
			} else if (path[0] !== _slash) {
				path = _slash + path;
			}
			break;
	}
	return path;
}

95 96 97 98
const _empty = '';
const _slash = '/';
const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;

E
Erich Gamma 已提交
99 100
/**
 * Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
101
 * This class is a simple parser which creates the basic component parts
E
Erich Gamma 已提交
102 103 104
 * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
 * and encoding.
 *
105
 *       foo://example.com:8042/over/there?name=ferret#nose
E
Erich Gamma 已提交
106 107 108 109 110 111 112
 *       \_/   \______________/\_________/ \_________/ \__/
 *        |           |            |            |        |
 *     scheme     authority       path        query   fragment
 *        |   _____________________|__
 *       / \ /                        \
 *       urn:example:animal:ferret:nose
 */
113
export class URI implements UriComponents {
E
Erich Gamma 已提交
114

115 116 117 118 119 120 121 122 123 124 125
	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 已提交
126 127 128 129
			&& typeof (<URI>thing).scheme === 'string'
			&& typeof (<URI>thing).fsPath === 'function'
			&& typeof (<URI>thing).with === 'function'
			&& typeof (<URI>thing).toString === 'function';
130 131
	}

E
Erich Gamma 已提交
132 133 134 135
	/**
	 * scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'.
	 * The part before the first colon.
	 */
J
Johannes Rieken 已提交
136
	readonly scheme: string;
E
Erich Gamma 已提交
137 138 139 140 141

	/**
	 * 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 已提交
142
	readonly authority: string;
E
Erich Gamma 已提交
143 144 145 146

	/**
	 * path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.
	 */
J
Johannes Rieken 已提交
147
	readonly path: string;
E
Erich Gamma 已提交
148 149 150 151

	/**
	 * query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'.
	 */
J
Johannes Rieken 已提交
152
	readonly query: string;
E
Erich Gamma 已提交
153 154 155 156

	/**
	 * fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'.
	 */
J
Johannes Rieken 已提交
157 158 159 160 161
	readonly fragment: string;

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

J
Johannes Rieken 已提交
164 165 166 167
	/**
	 * @internal
	 */
	protected constructor(components: UriComponents);
J
Johannes Rieken 已提交
168

J
Johannes Rieken 已提交
169 170 171
	/**
	 * @internal
	 */
J
Johannes Rieken 已提交
172
	protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) {
J
Johannes Rieken 已提交
173 174 175 176 177 178 179 180 181 182 183

		if (typeof schemeOrData === 'object') {
			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;
			// no validation because it's this URI
			// that creates uri components.
			// _validateUri(this);
		} else {
184
			this.scheme = _schemeFix(schemeOrData, _strict);
J
Johannes Rieken 已提交
185
			this.authority = authority || _empty;
186
			this.path = _referenceResolution(this.scheme, path || _empty);
J
Johannes Rieken 已提交
187 188
			this.query = query || _empty;
			this.fragment = fragment || _empty;
189

190
			_validateUri(this, _strict);
J
Johannes Rieken 已提交
191
		}
E
Erich Gamma 已提交
192 193 194 195 196
	}

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

	/**
P
Pascal Borreli 已提交
197
	 * Returns a string representing the corresponding file system path of this URI.
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
	 * 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 已提交
219
	 */
J
Johannes Rieken 已提交
220
	get fsPath(): string {
221 222 223
		// if (this.scheme !== 'file') {
		// 	console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`);
		// }
224
		return _makeFsPath(this);
E
Erich Gamma 已提交
225 226 227 228
	}

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

J
Johannes Rieken 已提交
229
	with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null }): URI {
230 231 232 233 234

		if (!change) {
			return this;
		}

235
		let { scheme, authority, path, query, fragment } = change;
R
Rob Lourens 已提交
236
		if (scheme === undefined) {
237
			scheme = this.scheme;
J
Johannes Rieken 已提交
238
		} else if (scheme === null) {
J
Johannes Rieken 已提交
239
			scheme = _empty;
240
		}
R
Rob Lourens 已提交
241
		if (authority === undefined) {
242
			authority = this.authority;
J
Johannes Rieken 已提交
243
		} else if (authority === null) {
J
Johannes Rieken 已提交
244
			authority = _empty;
245
		}
R
Rob Lourens 已提交
246
		if (path === undefined) {
247
			path = this.path;
J
Johannes Rieken 已提交
248
		} else if (path === null) {
J
Johannes Rieken 已提交
249
			path = _empty;
250
		}
R
Rob Lourens 已提交
251
		if (query === undefined) {
252
			query = this.query;
J
Johannes Rieken 已提交
253
		} else if (query === null) {
J
Johannes Rieken 已提交
254
			query = _empty;
255
		}
R
Rob Lourens 已提交
256
		if (fragment === undefined) {
257
			fragment = this.fragment;
J
Johannes Rieken 已提交
258
		} else if (fragment === null) {
J
Johannes Rieken 已提交
259
			fragment = _empty;
260
		}
261 262 263 264 265 266 267 268 269 270

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

			return this;
		}

271
		return new _URI(scheme, authority, path, query, fragment);
E
Erich Gamma 已提交
272 273 274 275
	}

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

276 277 278 279 280 281
	/**
	 * 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`).
	 */
J
Johannes Rieken 已提交
282
	static parse(value: string, _strict: boolean = false): URI {
283
		const match = _regexp.exec(value);
J
Johannes Rieken 已提交
284
		if (!match) {
285
			return new _URI(_empty, _empty, _empty, _empty, _empty);
J
Johannes Rieken 已提交
286
		}
287
		return new _URI(
288 289 290 291 292
			match[2] || _empty,
			decodeURIComponent(match[4] || _empty),
			decodeURIComponent(match[5] || _empty),
			decodeURIComponent(match[7] || _empty),
			decodeURIComponent(match[9] || _empty),
293
			_strict
J
Johannes Rieken 已提交
294
		);
E
Erich Gamma 已提交
295 296
	}

297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
	/**
	 * 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 已提交
318
	static file(path: string): URI {
E
Erich Gamma 已提交
319

320
		let authority = _empty;
321

322
		// normalize to fwd-slashes on windows,
A
typo  
Andre Weinand 已提交
323
		// on other systems bwd-slashes are valid
324
		// filename character, eg /f\oo/ba\r.txt
325
		if (isWindows) {
326
			path = path.replace(/\\/g, _slash);
327
		}
328 329 330

		// check for authority as used in UNC shares
		// or use the path as given
J
Johannes Rieken 已提交
331
		if (path[0] === _slash && path[1] === _slash) {
332
			const idx = path.indexOf(_slash, 2);
333
			if (idx === -1) {
J
Johannes Rieken 已提交
334
				authority = path.substring(2);
J
Johannes Rieken 已提交
335
				path = _slash;
336
			} else {
J
Johannes Rieken 已提交
337
				authority = path.substring(2, idx);
J
Johannes Rieken 已提交
338
				path = path.substring(idx) || _slash;
339 340 341
			}
		}

342
		return new _URI('file', authority, path, _empty, _empty);
E
Erich Gamma 已提交
343 344
	}

J
Johannes Rieken 已提交
345
	static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
346
		return new _URI(
J
Johannes Rieken 已提交
347 348 349 350 351 352
			components.scheme,
			components.authority,
			components.path,
			components.query,
			components.fragment,
		);
E
Erich Gamma 已提交
353 354 355 356
	}

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

357
	/**
A
Anuj 已提交
358
	 * Creates a string representation for this URI. It's guaranteed that calling
359 360 361 362 363 364
	 * `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.
365
	 *
366
	 * @param skipEncoding Do not encode the result, default is `false`
367
	 */
J
Johannes Rieken 已提交
368
	toString(skipEncoding: boolean = false): string {
369
		return _asFormatted(this, skipEncoding);
370 371
	}

J
Johannes Rieken 已提交
372
	toJSON(): UriComponents {
373
		return this;
374 375
	}

J
Johannes Rieken 已提交
376
	static revive(data: UriComponents | URI): URI;
377 378 379 380
	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 已提交
381
		if (!data) {
382
			return data;
J
Johannes Rieken 已提交
383 384 385
		} else if (data instanceof URI) {
			return data;
		} else {
386
			const result = new _URI(data);
387
			result._formatted = (<UriState>data).external;
J
Johannes Rieken 已提交
388
			result._fsPath = (<UriState>data).fsPathBsl === (isWindows || undefined) ? (<UriState>data).fsPath : null;
J
Johannes Rieken 已提交
389 390
			return result;
		}
391 392 393
	}
}

J
Johannes Rieken 已提交
394
export interface UriComponents {
395 396 397 398 399 400 401 402
	scheme: string;
	authority: string;
	path: string;
	query: string;
	fragment: string;
}

interface UriState extends UriComponents {
403
	$mid: number;
404
	external: string;
J
Johannes Rieken 已提交
405 406
	fsPath: string;
	fsPathBsl: true | undefined;
407 408
}

409

410 411
// tslint:disable-next-line:class-name
class _URI extends URI {
412

413 414
	_formatted: string | null = null;
	_fsPath: string | null = null;
415 416 417

	get fsPath(): string {
		if (!this._fsPath) {
418
			this._fsPath = _makeFsPath(this);
419 420 421 422
		}
		return this._fsPath;
	}

J
Johannes Rieken 已提交
423
	toString(skipEncoding: boolean = false): string {
424
		if (!skipEncoding) {
425
			if (!this._formatted) {
426
				this._formatted = _asFormatted(this, false);
427 428 429 430
			}
			return this._formatted;
		} else {
			// we don't cache that
431
			return _asFormatted(this, true);
J
Johannes Rieken 已提交
432 433
		}
	}
434

J
Johannes Rieken 已提交
435
	toJSON(): UriComponents {
436
		const res = <UriState>{
437
			$mid: 1
438 439 440 441
		};
		// cached state
		if (this._fsPath) {
			res.fsPath = this._fsPath;
J
Johannes Rieken 已提交
442
			res.fsPathBsl = isWindows || undefined;
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
		}
		if (this._formatted) {
			res.external = this._formatted;
		}
		// 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;
	}
465 466
}

467
// reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2
468
const encodeTable: { [ch: number]: string } = {
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
	[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',
};

J
Johannes Rieken 已提交
492
function encodeURIComponentFast(uriComponent: string, allowSlash: boolean): string {
J
Johannes Rieken 已提交
493
	let res: string | undefined = undefined;
494 495
	let nativeEncodePos = -1;

J
Johannes Rieken 已提交
496
	for (let pos = 0; pos < uriComponent.length; pos++) {
497
		const code = uriComponent.charCodeAt(pos);
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526

		// 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);
			}

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

			// check with default table first
527
			const escaped = encodeTable[code];
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
			if (escaped !== undefined) {

				// check if we are delaying native encode
				if (nativeEncodePos !== -1) {
					res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos));
					nativeEncodePos = -1;
				}

				// append escaped variant to result
				res += escaped;

			} else if (nativeEncodePos === -1) {
				// use native encode only when needed
				nativeEncodePos = pos;
			}
		}
	}

	if (nativeEncodePos !== -1) {
		res += encodeURIComponent(uriComponent.substring(nativeEncodePos));
	}

	return res !== undefined ? res : uriComponent;
}

function encodeURIComponentMinimal(path: string): string {
554
	let res: string | undefined = undefined;
555
	for (let pos = 0; pos < path.length; pos++) {
556
		const code = path.charCodeAt(pos);
557 558 559 560 561 562 563 564 565 566 567 568 569
		if (code === CharCode.Hash || code === CharCode.QuestionMark) {
			if (res === undefined) {
				res = path.substr(0, pos);
			}
			res += encodeTable[code];
		} else {
			if (res !== undefined) {
				res += path[pos];
			}
		}
	}
	return res !== undefined ? res : path;
}
J
Johannes Rieken 已提交
570

571 572 573 574 575 576
/**
 * Compute `fsPath` for the given uri
 */
function _makeFsPath(uri: URI): string {

	let value: string;
577
	if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') {
578 579
		// unc path: file://shares/c$/far/boo
		value = `//${uri.authority}${uri.path}`;
J
Johannes Rieken 已提交
580 581 582 583 584
	} 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
	) {
585 586 587 588 589 590
		// windows drive letter: file:///c:/far/boo
		value = uri.path[1].toLowerCase() + uri.path.substr(2);
	} else {
		// other path
		value = uri.path;
	}
591
	if (isWindows) {
592 593 594 595
		value = value.replace(/\//g, '\\');
	}
	return value;
}
596

597 598 599
/**
 * Create the external version of a uri
 */
600
function _asFormatted(uri: URI, skipEncoding: boolean): string {
601

602
	const encoder = !skipEncoding
603 604
		? encodeURIComponentFast
		: encodeURIComponentMinimal;
E
Erich Gamma 已提交
605

606
	let res = '';
607 608
	let { scheme, authority, path, query, fragment } = uri;
	if (scheme) {
609 610
		res += scheme;
		res += ':';
611 612
	}
	if (authority || scheme === 'file') {
613 614
		res += _slash;
		res += _slash;
615 616 617 618
	}
	if (authority) {
		let idx = authority.indexOf('@');
		if (idx !== -1) {
619
			// <user>@<auth>
620 621 622
			const userinfo = authority.substr(0, idx);
			authority = authority.substr(idx + 1);
			idx = userinfo.indexOf(':');
J
Johannes Rieken 已提交
623
			if (idx === -1) {
624
				res += encoder(userinfo, false);
J
Johannes Rieken 已提交
625
			} else {
626
				// <user>:<pass>@<auth>
627
				res += encoder(userinfo.substr(0, idx), false);
628
				res += ':';
629
				res += encoder(userinfo.substr(idx + 1), false);
E
Erich Gamma 已提交
630
			}
631
			res += '@';
J
Johannes Rieken 已提交
632
		}
633 634 635
		authority = authority.toLowerCase();
		idx = authority.indexOf(':');
		if (idx === -1) {
636
			res += encoder(authority, false);
637
		} else {
638
			// <auth>:<port>
639
			res += encoder(authority.substr(0, idx), false);
640
			res += authority.substr(idx);
J
Johannes Rieken 已提交
641
		}
642 643 644
	}
	if (path) {
		// lower-case windows drive letters in /C:/fff or C:/fff
645
		if (path.length >= 3 && path.charCodeAt(0) === CharCode.Slash && path.charCodeAt(2) === CharCode.Colon) {
646
			const code = path.charCodeAt(1);
647 648
			if (code >= CharCode.A && code <= CharCode.Z) {
				path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3
649
			}
650
		} else if (path.length >= 2 && path.charCodeAt(1) === CharCode.Colon) {
651
			const code = path.charCodeAt(0);
652 653
			if (code >= CharCode.A && code <= CharCode.Z) {
				path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "/c:".length === 3
654
			}
655
		}
656
		// encode the rest of the path
J
Johannes Rieken 已提交
657
		res += encoder(path, true);
658 659
	}
	if (query) {
660
		res += '?';
661
		res += encoder(query, false);
662 663
	}
	if (fragment) {
664
		res += '#';
J
Johannes Rieken 已提交
665
		res += !skipEncoding ? encodeURIComponentFast(fragment, false) : fragment;
E
Erich Gamma 已提交
666
	}
667
	return res;
E
Erich Gamma 已提交
668
}