596 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			596 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*---------------------------------------------------------------------------------------------
 | |
|  *  Copyright (c) Microsoft Corporation. All rights reserved.
 | |
|  *  Licensed under the MIT License. See License.txt in the project root for license information.
 | |
|  *--------------------------------------------------------------------------------------------*/
 | |
| 'use strict';
 | |
| Object.defineProperty(exports, "__esModule", { value: true });
 | |
| exports.uriToFsPath = exports.URI = void 0;
 | |
| const platform_1 = require("./platform");
 | |
| const _schemePattern = /^\w[\w\d+.-]*$/;
 | |
| const _singleSlashStart = /^\//;
 | |
| const _doubleSlashStart = /^\/\//;
 | |
| function _validateUri(ret, _strict) {
 | |
|     // 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}"}`);
 | |
|     }
 | |
|     // 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 ("//")');
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| // 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, _strict) {
 | |
|     if (!scheme && !_strict) {
 | |
|         return 'file';
 | |
|     }
 | |
|     return scheme;
 | |
| }
 | |
| // implements a bit of https://tools.ietf.org/html/rfc3986#section-5
 | |
| function _referenceResolution(scheme, path) {
 | |
|     // 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;
 | |
| }
 | |
| 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
 | |
|  * ```
 | |
|  */
 | |
| class URI {
 | |
|     static isUri(thing) {
 | |
|         if (thing instanceof URI) {
 | |
|             return true;
 | |
|         }
 | |
|         if (!thing) {
 | |
|             return false;
 | |
|         }
 | |
|         return typeof thing.authority === 'string'
 | |
|             && typeof thing.fragment === 'string'
 | |
|             && typeof thing.path === 'string'
 | |
|             && typeof thing.query === 'string'
 | |
|             && typeof thing.scheme === 'string'
 | |
|             && typeof thing.fsPath === 'string'
 | |
|             && typeof thing.with === 'function'
 | |
|             && typeof thing.toString === 'function';
 | |
|     }
 | |
|     /**
 | |
|      * scheme is the 'http' part of 'http://www.example.com/some/path?query#fragment'.
 | |
|      * The part before the first colon.
 | |
|      */
 | |
|     scheme;
 | |
|     /**
 | |
|      * 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.
 | |
|      */
 | |
|     authority;
 | |
|     /**
 | |
|      * path is the '/some/path' part of 'http://www.example.com/some/path?query#fragment'.
 | |
|      */
 | |
|     path;
 | |
|     /**
 | |
|      * query is the 'query' part of 'http://www.example.com/some/path?query#fragment'.
 | |
|      */
 | |
|     query;
 | |
|     /**
 | |
|      * fragment is the 'fragment' part of 'http://www.example.com/some/path?query#fragment'.
 | |
|      */
 | |
|     fragment;
 | |
|     /**
 | |
|      * @internal
 | |
|      */
 | |
|     constructor(schemeOrData, authority, path, query, fragment, _strict = false) {
 | |
|         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 {
 | |
|             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() {
 | |
|         // if (this.scheme !== 'file') {
 | |
|         // 	console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`);
 | |
|         // }
 | |
|         return uriToFsPath(this, false);
 | |
|     }
 | |
|     // ---- modify to new -------------------------
 | |
|     with(change) {
 | |
|         if (!change) {
 | |
|             return this;
 | |
|         }
 | |
|         let { scheme, authority, path, query, fragment } = change;
 | |
|         if (scheme === undefined) {
 | |
|             scheme = this.scheme;
 | |
|         }
 | |
|         else if (scheme === null) {
 | |
|             scheme = _empty;
 | |
|         }
 | |
|         if (authority === undefined) {
 | |
|             authority = this.authority;
 | |
|         }
 | |
|         else if (authority === null) {
 | |
|             authority = _empty;
 | |
|         }
 | |
|         if (path === undefined) {
 | |
|             path = this.path;
 | |
|         }
 | |
|         else if (path === null) {
 | |
|             path = _empty;
 | |
|         }
 | |
|         if (query === undefined) {
 | |
|             query = this.query;
 | |
|         }
 | |
|         else if (query === null) {
 | |
|             query = _empty;
 | |
|         }
 | |
|         if (fragment === undefined) {
 | |
|             fragment = this.fragment;
 | |
|         }
 | |
|         else if (fragment === null) {
 | |
|             fragment = _empty;
 | |
|         }
 | |
|         if (scheme === this.scheme
 | |
|             && authority === this.authority
 | |
|             && path === this.path
 | |
|             && query === this.query
 | |
|             && fragment === this.fragment) {
 | |
|             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, _strict = false) {
 | |
|         const match = _regexp.exec(value);
 | |
|         if (!match) {
 | |
|             return new Uri(_empty, _empty, _empty, _empty, _empty);
 | |
|         }
 | |
|         return new Uri(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) {
 | |
|         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 (platform_1.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) {
 | |
|             const idx = path.indexOf(_slash, 2);
 | |
|             if (idx === -1) {
 | |
|                 authority = path.substring(2);
 | |
|                 path = _slash;
 | |
|             }
 | |
|             else {
 | |
|                 authority = path.substring(2, idx);
 | |
|                 path = path.substring(idx) || _slash;
 | |
|             }
 | |
|         }
 | |
|         return new Uri('file', authority, path, _empty, _empty);
 | |
|     }
 | |
|     static from(components) {
 | |
|         const result = new Uri(components.scheme, components.authority, components.path, components.query, components.fragment);
 | |
|         _validateUri(result, true);
 | |
|         return result;
 | |
|     }
 | |
|     // ---- 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 = false) {
 | |
|         return _asFormatted(this, skipEncoding);
 | |
|     }
 | |
|     toJSON() {
 | |
|         return this;
 | |
|     }
 | |
|     static revive(data) {
 | |
|         if (!data) {
 | |
|             return data;
 | |
|         }
 | |
|         else if (data instanceof URI) {
 | |
|             return data;
 | |
|         }
 | |
|         else {
 | |
|             const result = new Uri(data);
 | |
|             result._formatted = data.external;
 | |
|             result._fsPath = data._sep === _pathSepMarker ? data.fsPath : null;
 | |
|             return result;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| exports.URI = URI;
 | |
| const _pathSepMarker = platform_1.isWindows ? 1 : undefined;
 | |
| // This class exists so that URI is compatible with vscode.Uri (API).
 | |
| class Uri extends URI {
 | |
|     _formatted = null;
 | |
|     _fsPath = null;
 | |
|     get fsPath() {
 | |
|         if (!this._fsPath) {
 | |
|             this._fsPath = uriToFsPath(this, false);
 | |
|         }
 | |
|         return this._fsPath;
 | |
|     }
 | |
|     toString(skipEncoding = false) {
 | |
|         if (!skipEncoding) {
 | |
|             if (!this._formatted) {
 | |
|                 this._formatted = _asFormatted(this, false);
 | |
|             }
 | |
|             return this._formatted;
 | |
|         }
 | |
|         else {
 | |
|             // we don't cache that
 | |
|             return _asFormatted(this, true);
 | |
|         }
 | |
|     }
 | |
|     toJSON() {
 | |
|         const res = {
 | |
|             $mid: 1
 | |
|         };
 | |
|         // cached state
 | |
|         if (this._fsPath) {
 | |
|             res.fsPath = this._fsPath;
 | |
|             res._sep = _pathSepMarker;
 | |
|         }
 | |
|         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;
 | |
|     }
 | |
| }
 | |
| // reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2
 | |
| const encodeTable = {
 | |
|     [58 /* CharCode.Colon */]: '%3A',
 | |
|     [47 /* CharCode.Slash */]: '%2F',
 | |
|     [63 /* CharCode.QuestionMark */]: '%3F',
 | |
|     [35 /* CharCode.Hash */]: '%23',
 | |
|     [91 /* CharCode.OpenSquareBracket */]: '%5B',
 | |
|     [93 /* CharCode.CloseSquareBracket */]: '%5D',
 | |
|     [64 /* CharCode.AtSign */]: '%40',
 | |
|     [33 /* CharCode.ExclamationMark */]: '%21',
 | |
|     [36 /* CharCode.DollarSign */]: '%24',
 | |
|     [38 /* CharCode.Ampersand */]: '%26',
 | |
|     [39 /* CharCode.SingleQuote */]: '%27',
 | |
|     [40 /* CharCode.OpenParen */]: '%28',
 | |
|     [41 /* CharCode.CloseParen */]: '%29',
 | |
|     [42 /* CharCode.Asterisk */]: '%2A',
 | |
|     [43 /* CharCode.Plus */]: '%2B',
 | |
|     [44 /* CharCode.Comma */]: '%2C',
 | |
|     [59 /* CharCode.Semicolon */]: '%3B',
 | |
|     [61 /* CharCode.Equals */]: '%3D',
 | |
|     [32 /* CharCode.Space */]: '%20',
 | |
| };
 | |
| function encodeURIComponentFast(uriComponent, isPath, isAuthority) {
 | |
|     let res = 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 >= 97 /* CharCode.a */ && code <= 122 /* CharCode.z */)
 | |
|             || (code >= 65 /* CharCode.A */ && code <= 90 /* CharCode.Z */)
 | |
|             || (code >= 48 /* CharCode.Digit0 */ && code <= 57 /* CharCode.Digit9 */)
 | |
|             || code === 45 /* CharCode.Dash */
 | |
|             || code === 46 /* CharCode.Period */
 | |
|             || code === 95 /* CharCode.Underline */
 | |
|             || code === 126 /* CharCode.Tilde */
 | |
|             || (isPath && code === 47 /* CharCode.Slash */)
 | |
|             || (isAuthority && code === 91 /* CharCode.OpenSquareBracket */)
 | |
|             || (isAuthority && code === 93 /* CharCode.CloseSquareBracket */)
 | |
|             || (isAuthority && code === 58 /* CharCode.Colon */)) {
 | |
|             // 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
 | |
|             const escaped = encodeTable[code];
 | |
|             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) {
 | |
|     let res = undefined;
 | |
|     for (let pos = 0; pos < path.length; pos++) {
 | |
|         const code = path.charCodeAt(pos);
 | |
|         if (code === 35 /* CharCode.Hash */ || code === 63 /* 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;
 | |
| }
 | |
| /**
 | |
|  * Compute `fsPath` for the given uri
 | |
|  */
 | |
| function uriToFsPath(uri, keepDriveLetterCasing) {
 | |
|     let value;
 | |
|     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) === 47 /* CharCode.Slash */
 | |
|         && (uri.path.charCodeAt(1) >= 65 /* CharCode.A */ && uri.path.charCodeAt(1) <= 90 /* CharCode.Z */ || uri.path.charCodeAt(1) >= 97 /* CharCode.a */ && uri.path.charCodeAt(1) <= 122 /* CharCode.z */)
 | |
|         && uri.path.charCodeAt(2) === 58 /* CharCode.Colon */) {
 | |
|         if (!keepDriveLetterCasing) {
 | |
|             // windows drive letter: file:///c:/far/boo
 | |
|             value = uri.path[1].toLowerCase() + uri.path.substr(2);
 | |
|         }
 | |
|         else {
 | |
|             value = uri.path.substr(1);
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         // other path
 | |
|         value = uri.path;
 | |
|     }
 | |
|     if (platform_1.isWindows) {
 | |
|         value = value.replace(/\//g, '\\');
 | |
|     }
 | |
|     return value;
 | |
| }
 | |
| exports.uriToFsPath = uriToFsPath;
 | |
| /**
 | |
|  * Create the external version of a uri
 | |
|  */
 | |
| function _asFormatted(uri, skipEncoding) {
 | |
|     const encoder = !skipEncoding
 | |
|         ? encodeURIComponentFast
 | |
|         : encodeURIComponentMinimal;
 | |
|     let res = '';
 | |
|     let { scheme, authority, path, query, fragment } = uri;
 | |
|     if (scheme) {
 | |
|         res += scheme;
 | |
|         res += ':';
 | |
|     }
 | |
|     if (authority || scheme === 'file') {
 | |
|         res += _slash;
 | |
|         res += _slash;
 | |
|     }
 | |
|     if (authority) {
 | |
|         let idx = authority.indexOf('@');
 | |
|         if (idx !== -1) {
 | |
|             // <user>@<auth>
 | |
|             const userinfo = authority.substr(0, idx);
 | |
|             authority = authority.substr(idx + 1);
 | |
|             idx = userinfo.lastIndexOf(':');
 | |
|             if (idx === -1) {
 | |
|                 res += encoder(userinfo, false, false);
 | |
|             }
 | |
|             else {
 | |
|                 // <user>:<pass>@<auth>
 | |
|                 res += encoder(userinfo.substr(0, idx), false, false);
 | |
|                 res += ':';
 | |
|                 res += encoder(userinfo.substr(idx + 1), false, true);
 | |
|             }
 | |
|             res += '@';
 | |
|         }
 | |
|         authority = authority.toLowerCase();
 | |
|         idx = authority.lastIndexOf(':');
 | |
|         if (idx === -1) {
 | |
|             res += encoder(authority, false, true);
 | |
|         }
 | |
|         else {
 | |
|             // <auth>:<port>
 | |
|             res += encoder(authority.substr(0, idx), false, true);
 | |
|             res += authority.substr(idx);
 | |
|         }
 | |
|     }
 | |
|     if (path) {
 | |
|         // lower-case windows drive letters in /C:/fff or C:/fff
 | |
|         if (path.length >= 3 && path.charCodeAt(0) === 47 /* CharCode.Slash */ && path.charCodeAt(2) === 58 /* CharCode.Colon */) {
 | |
|             const code = path.charCodeAt(1);
 | |
|             if (code >= 65 /* CharCode.A */ && code <= 90 /* CharCode.Z */) {
 | |
|                 path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3
 | |
|             }
 | |
|         }
 | |
|         else if (path.length >= 2 && path.charCodeAt(1) === 58 /* CharCode.Colon */) {
 | |
|             const code = path.charCodeAt(0);
 | |
|             if (code >= 65 /* CharCode.A */ && code <= 90 /* CharCode.Z */) {
 | |
|                 path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "/c:".length === 3
 | |
|             }
 | |
|         }
 | |
|         // encode the rest of the path
 | |
|         res += encoder(path, true, false);
 | |
|     }
 | |
|     if (query) {
 | |
|         res += '?';
 | |
|         res += encoder(query, false, false);
 | |
|     }
 | |
|     if (fragment) {
 | |
|         res += '#';
 | |
|         res += !skipEncoding ? encodeURIComponentFast(fragment, false, false) : fragment;
 | |
|     }
 | |
|     return res;
 | |
| }
 | |
| // --- decode
 | |
| function decodeURIComponentGraceful(str) {
 | |
|     try {
 | |
|         return decodeURIComponent(str);
 | |
|     }
 | |
|     catch {
 | |
|         if (str.length > 3) {
 | |
|             return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3));
 | |
|         }
 | |
|         else {
 | |
|             return str;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
 | |
| function percentDecode(str) {
 | |
|     if (!str.match(_rEncodedAsHex)) {
 | |
|         return str;
 | |
|     }
 | |
|     return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
 | |
| }
 | 
