提交 58479e80 编写于 作者: J Johannes Rieken

Revert "Merge pull request #83060 from microsoft/joh/uri-parse"

This reverts commit 30886e2f, reversing
changes made to 525f1e47.
上级 f478d910
......@@ -338,34 +338,6 @@ export function compareIgnoreCase(a: string, b: string): number {
}
}
/**
* [0-9]
*/
export function isAsciiDigit(code: number): boolean {
return code >= CharCode.Digit0 && code <= CharCode.Digit9;
}
/**
* [a-f]
*/
export function isLowerAsciiHex(code: number): boolean {
return code >= CharCode.a && code <= CharCode.f;
}
/**
* [A-F]
*/
export function isUpperAsciiHex(code: number): boolean {
return code >= CharCode.A && code <= CharCode.F;
}
/**
* [0-9a-fA-F]
*/
export function isAsciiHex(code: number): boolean {
return isAsciiDigit(code) || isLowerAsciiHex(code) || isUpperAsciiHex(code);
}
export function isLowerAsciiLetter(code: number): boolean {
return code >= CharCode.a && code <= CharCode.z;
}
......@@ -374,7 +346,7 @@ export function isUpperAsciiLetter(code: number): boolean {
return code >= CharCode.A && code <= CharCode.Z;
}
export function isAsciiLetter(code: number): boolean {
function isAsciiLetter(code: number): boolean {
return isLowerAsciiLetter(code) || isUpperAsciiLetter(code);
}
......
......@@ -5,9 +5,10 @@
import { isWindows } from 'vs/base/common/platform';
import { CharCode } from 'vs/base/common/charCode';
import { isHighSurrogate, isLowSurrogate, isLowerAsciiHex, isAsciiLetter } from 'vs/base/common/strings';
const _schemeRegExp = /^\w[\w\d+.-]*$/;
const _schemePattern = /^\w[\w\d+.-]*$/;
const _singleSlashStart = /^\//;
const _doubleSlashStart = /^\/\//;
function _validateUri(ret: URI): void {
......@@ -18,7 +19,7 @@ function _validateUri(ret: URI): void {
// scheme, https://tools.ietf.org/html/rfc3986#section-3.1
// ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
if (ret.scheme && !_schemeRegExp.test(ret.scheme)) {
if (ret.scheme && !_schemePattern.test(ret.scheme)) {
throw new Error('[UriError]: Scheme contains illegal characters.');
}
......@@ -29,11 +30,11 @@ function _validateUri(ret: URI): void {
// with two slash characters ("//").
if (ret.path) {
if (ret.authority) {
if (ret.path.charCodeAt(0) !== CharCode.Slash) {
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 (ret.path.charCodeAt(0) === CharCode.Slash && ret.path.charCodeAt(1) === CharCode.Slash) {
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 ("//")');
}
}
......@@ -41,14 +42,10 @@ function _validateUri(ret: URI): void {
}
// graceful behaviour when scheme is missing: fallback to using 'file'-scheme
function _schemeFix(scheme: string, strict?: boolean): string {
function _schemeFix(scheme: string): string {
if (!scheme) {
if (strict) {
throw new Error('[UriError]: A scheme must be provided');
} else {
// console.trace('BAD uri lacks scheme, falling back to file-scheme.');
scheme = 'file';
}
console.trace('BAD uri lacks scheme, falling back to file-scheme.');
scheme = 'file';
}
return scheme;
}
......@@ -65,29 +62,18 @@ function _referenceResolution(scheme: string, path: string): string {
case 'http':
case 'file':
if (!path) {
path = '/';
} else if (path[0].charCodeAt(0) !== CharCode.Slash) {
path = '/' + path;
path = _slash;
} else if (path[0] !== _slash) {
path = _slash + path;
}
break;
}
return path;
}
const _uriRegExp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
const enum MatchIndex {
scheme = 2,
authority = 4,
path = 5,
query = 7,
fragment = 9
}
const _percentRegExp = /%/g;
const _hashRegExp = /#/g;
const _backslashRegExp = /\\/g;
const _slashRegExp = /\//g;
const _empty = '';
const _slash = '/';
const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
/**
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
......@@ -165,20 +151,20 @@ export class URI implements UriComponents {
protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string) {
if (typeof schemeOrData === 'object') {
this.scheme = schemeOrData.scheme || '';
this.authority = schemeOrData.authority || '';
this.path = schemeOrData.path || '';
this.query = schemeOrData.query || '';
this.fragment = schemeOrData.fragment || '';
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);
this.authority = authority || '';
this.path = _referenceResolution(this.scheme, path || '');
this.query = query || '';
this.fragment = fragment || '';
this.authority = authority || _empty;
this.path = _referenceResolution(this.scheme, path || _empty);
this.query = query || _empty;
this.fragment = fragment || _empty;
_validateUri(this);
}
......@@ -211,7 +197,10 @@ export class URI implements UriComponents {
* with URIs that represent files on disk (`file` scheme).
*/
get fsPath(): string {
return _toFsPath(this.scheme, this.authority, this.path);
// if (this.scheme !== 'file') {
// console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`);
// }
return _makeFsPath(this);
}
// ---- modify to new -------------------------
......@@ -226,27 +215,27 @@ export class URI implements UriComponents {
if (scheme === undefined) {
scheme = this.scheme;
} else if (scheme === null) {
scheme = '';
scheme = _empty;
}
if (authority === undefined) {
authority = this.authority;
} else if (authority === null) {
authority = '';
authority = _empty;
}
if (path === undefined) {
path = this.path;
} else if (path === null) {
path = '';
path = _empty;
}
if (query === undefined) {
query = this.query;
} else if (query === null) {
query = '';
query = _empty;
}
if (fragment === undefined) {
fragment = this.fragment;
} else if (fragment === null) {
fragment = '';
fragment = _empty;
}
if (scheme === this.scheme
......@@ -267,31 +256,20 @@ export class URI implements UriComponents {
* Creates a new URI from a string, e.g. `http://www.msft.com/some/path`,
* `file:///usr/home`, or `scheme:with/path`.
*
* *Note:* When the input lacks a scheme then `file` is used.
*
* @param value A string which represents an URI (see `URI#toString`).
*/
static parse(value: string, strict?: boolean): URI {
const match = _uriRegExp.exec(value);
static parse(value: string): URI {
const match = _regexp.exec(value);
if (!match) {
throw new Error(`[UriError]: Invalid input: ${value}`);
return new _URI(_empty, _empty, _empty, _empty, _empty);
}
const scheme = _schemeFix(match[MatchIndex.scheme], strict) || '';
const authority = match[MatchIndex.authority] || '';
const path = _referenceResolution(scheme, match[MatchIndex.path] || '');
const query = match[MatchIndex.query] || '';
const fragment = match[MatchIndex.fragment] || '';
const result = new _URI(
scheme,
percentDecode(authority),
percentDecode(path),
percentDecode(query),
percentDecode(fragment),
return new _URI(
match[2] || _empty,
decodeURIComponent(match[4] || _empty),
decodeURIComponent(match[5] || _empty),
decodeURIComponent(match[7] || _empty),
decodeURIComponent(match[9] || _empty)
);
result._external = _toExternal(_normalEncoder, scheme, authority, path, query, fragment);
return result;
}
/**
......@@ -317,41 +295,29 @@ export class URI implements UriComponents {
*/
static file(path: string): URI {
let authority = '';
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(_backslashRegExp, '/');
path = path.replace(/\\/g, _slash);
}
// check for authority as used in UNC shares
// or use the path as given
if (path.charCodeAt(0) === CharCode.Slash && path.charCodeAt(1) === CharCode.Slash) {
const idx = path.indexOf('/', 2);
if (path[0] === _slash && path[1] === _slash) {
const idx = path.indexOf(_slash, 2);
if (idx === -1) {
authority = path.substring(2);
path = '/';
path = _slash;
} else {
authority = path.substring(2, idx);
path = path.substring(idx) || '/';
path = path.substring(idx) || _slash;
}
}
// ensures that path starts with /
path = _referenceResolution('file', path);
// escape some vital characters
authority = authority.replace(_percentRegExp, '%25');
path = path.replace(_percentRegExp, '%25');
path = path.replace(_hashRegExp, '%23');
if (!isWindows) {
path = path.replace(_backslashRegExp, '%5C');
}
return URI.parse('file://' + authority + path);
return new _URI('file', authority, path, _empty, _empty);
}
static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string; }): URI {
......@@ -378,7 +344,7 @@ export class URI implements UriComponents {
* @param skipEncoding Do not encode the result, default is `false`
*/
toString(skipEncoding: boolean = false): string {
return _toExternal(skipEncoding ? _minimalEncoder : _normalEncoder, this.scheme, this.authority, this.path, this.query, this.fragment);
return _asFormatted(this, skipEncoding);
}
toJSON(): UriComponents {
......@@ -396,7 +362,7 @@ export class URI implements UriComponents {
return data;
} else {
const result = new _URI(data);
result._external = (<UriState>data).external;
result._formatted = (<UriState>data).external;
result._fsPath = (<UriState>data)._sep === _pathSepMarker ? (<UriState>data).fsPath : null;
return result;
}
......@@ -423,25 +389,26 @@ const _pathSepMarker = isWindows ? 1 : undefined;
// tslint:disable-next-line:class-name
class _URI extends URI {
_external: string | null = null;
_formatted: string | null = null;
_fsPath: string | null = null;
get fsPath(): string {
if (!this._fsPath) {
this._fsPath = _toFsPath(this.scheme, this.authority, this.path);
this._fsPath = _makeFsPath(this);
}
return this._fsPath;
}
toString(skipEncoding: boolean = false): string {
if (skipEncoding) {
if (!skipEncoding) {
if (!this._formatted) {
this._formatted = _asFormatted(this, false);
}
return this._formatted;
} else {
// we don't cache that
return _toExternal(_minimalEncoder, this.scheme, this.authority, this.path, this.query, this.fragment);
}
if (!this._external) {
this._external = _toExternal(_normalEncoder, this.scheme, this.authority, this.path, this.query, this.fragment);
return _asFormatted(this, true);
}
return this._external;
}
toJSON(): UriComponents {
......@@ -453,8 +420,8 @@ class _URI extends URI {
res.fsPath = this._fsPath;
res._sep = _pathSepMarker;
}
if (this._external) {
res.external = this._external;
if (this._formatted) {
res.external = this._formatted;
}
// uri components
if (this.path) {
......@@ -476,225 +443,205 @@ class _URI extends URI {
}
}
/**
* Compute `fsPath` for the given uri
*/
function _toFsPath(scheme: string, authority: string, path: string): string {
// 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);
}
let value: string;
if (authority && path.length > 1 && scheme === 'file') {
// unc path: file://shares/c$/far/boo
value = `//${authority}${path}`;
} else if (
path.charCodeAt(0) === CharCode.Slash
&& isAsciiLetter(path.charCodeAt(1))
&& path.charCodeAt(2) === CharCode.Colon
) {
// windows drive letter: file:///c:/far/boo
value = path[1].toLowerCase() + path.substr(2);
} else {
// other path
value = path;
}
if (isWindows) {
value = value.replace(_slashRegExp, '\\');
}
return value;
}
} 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) {
//#region ---- decode
// check if we are delaying native encode
if (nativeEncodePos !== -1) {
res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos));
nativeEncodePos = -1;
}
function decodeURIComponentGraceful(str: string): string {
try {
return decodeURIComponent(str);
} catch {
if (str.length > 3) {
return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3));
} else {
return str;
// append escaped variant to result
res += escaped;
} else if (nativeEncodePos === -1) {
// use native encode only when needed
nativeEncodePos = pos;
}
}
}
}
const _encodedAsHexRegExp = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
function percentDecode(str: string): string {
if (!str.match(_encodedAsHexRegExp)) {
return str;
if (nativeEncodePos !== -1) {
res += encodeURIComponent(uriComponent.substring(nativeEncodePos));
}
return str.replace(_encodedAsHexRegExp, (match) => decodeURIComponentGraceful(match));
}
//#endregion
//#region ---- encode
// https://url.spec.whatwg.org/#percent-encoded-bytes
// "The C0 control percent-encode set are the C0 controls and all code points greater than U+007E (~)."
function isC0ControlPercentEncodeSet(code: number): boolean {
return code <= 0x1F || code > 0x7E;
}
// "The fragment percent-encode set is the C0 control percent-encode set and U+0020 SPACE, U+0022 ("), U+003C (<), U+003E (>), and U+0060 (`)."
function isFragmentPercentEncodeSet(code: number): boolean {
return isC0ControlPercentEncodeSet(code)
|| code === 0x20 || code === 0x22 || code === 0x3C || code === 0x3E || code === 0x60;
}
// "The path percent-encode set is the fragment percent-encode set and U+0023 (#), U+003F (?), U+007B ({), and U+007D (})."
function isPathPercentEncodeSet(code: number): boolean {
return isFragmentPercentEncodeSet(code)
|| code === 0x23 || code === 0x3F || code === 0x7B || code === 0x7D;
}
// "The userinfo percent-encode set is the path percent-encode set and U+002F (/), U+003A (:), U+003B (;), U+003D (=), U+0040 (@), U+005B ([), U+005C (\), U+005D (]), U+005E (^), and U+007C (|)."
function isUserInfoPercentEncodeSet(code: number): boolean {
return isPathPercentEncodeSet(code)
|| code === 0x2F || code === 0x3A || code === 0x3B || code === 0x3D || code === 0x40
|| code === 0x5B || code === 0x5C || code === 0x5D || code === 0x5E || code === 0x7C;
}
// https://url.spec.whatwg.org/#query-state
function isQueryPrecentEncodeSet(code: number): boolean {
return code < 0x21 || code > 0x7E
|| code === 0x22 || code === 0x23 || code === 0x3C || code === 0x3E
|| code === 0x27; // <- todo@joh https://url.spec.whatwg.org/#is-special
}
// this is non-standard and uses for `URI.toString(true)`
function isHashOrQuestionMark(code: number): boolean {
return code === CharCode.Hash || code === CharCode.QuestionMark;
return res !== undefined ? res : uriComponent;
}
const _encodeTable: string[] = (function () {
let table: string[] = [];
for (let code = 0; code < 128; code++) {
if (code < 16) {
table[code] = `%0${code.toString(16).toUpperCase()}`;
} else {
table[code] = `%${code.toString(16).toUpperCase()}`;
}
}
return table;
})();
function percentEncode(str: string, mustEncode: (code: number) => boolean): string {
let lazyOutStr: string | undefined;
for (let pos = 0; pos < str.length; pos++) {
const code = str.charCodeAt(pos);
// invoke encodeURIComponent when needed
if (mustEncode(code)) {
if (!lazyOutStr) {
lazyOutStr = str.substr(0, pos);
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);
}
if (isHighSurrogate(code)) {
// Append encoded version of this surrogate pair (2 characters)
if (pos + 1 < str.length && isLowSurrogate(str.charCodeAt(pos + 1))) {
lazyOutStr += encodeURIComponent(str.substr(pos, 2));
pos += 1;
} else {
// broken surrogate pair
lazyOutStr += str.charAt(pos);
}
} else {
// Append encoded version of the current character, use lookup table
// to speed up repeated encoding of the same characters.
if (code < _encodeTable.length) {
lazyOutStr += _encodeTable[code];
} else {
lazyOutStr += encodeURIComponent(str.charAt(pos));
}
}
continue;
}
// normalize percent encoded sequences to upper case
// todo@joh also changes invalid sequences
if (code === CharCode.PercentSign
&& pos + 2 < str.length
&& (isLowerAsciiHex(str.charCodeAt(pos + 1)) || isLowerAsciiHex(str.charCodeAt(pos + 2)))
) {
if (!lazyOutStr) {
lazyOutStr = str.substr(0, pos);
res += encodeTable[code];
} else {
if (res !== undefined) {
res += path[pos];
}
lazyOutStr += '%' + str.substr(pos + 1, 2).toUpperCase();
pos += 2;
continue;
}
// once started, continue to build up lazy output
if (lazyOutStr) {
lazyOutStr += str.charAt(pos);
}
}
return lazyOutStr || str;
return res !== undefined ? res : path;
}
const enum EncodePart {
user, authority, path, query, fragment
}
const _normalEncoder: { (code: number): boolean }[] = [isUserInfoPercentEncodeSet, isC0ControlPercentEncodeSet, isPathPercentEncodeSet, isQueryPrecentEncodeSet, isFragmentPercentEncodeSet];
const _minimalEncoder: { (code: number): boolean }[] = [isHashOrQuestionMark, isHashOrQuestionMark, isHashOrQuestionMark, isHashOrQuestionMark, () => false];
/**
* Compute `fsPath` for the given uri
*/
function _makeFsPath(uri: URI): string {
const _driveLetterRegExp = /^(\/?[a-z])(:|%3a)/i;
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;
}
/**
* Create the external version of a uri
*/
function _toExternal(encoder: { (code: number): boolean }[], scheme: string, authority: string, path: string, query: string, fragment: string): string {
function _asFormatted(uri: URI, skipEncoding: boolean): string {
const encoder = !skipEncoding
? encodeURIComponentFast
: encodeURIComponentMinimal;
let res = '';
let { scheme, authority, path, query, fragment } = uri;
if (scheme) {
res += scheme;
res += ':';
}
if (authority || scheme === 'file') {
res += '//';
res += _slash;
res += _slash;
}
if (authority) {
const idxUserInfo = authority.indexOf('@');
if (idxUserInfo !== -1) {
// <user:token>
const userInfo = authority.substr(0, idxUserInfo);
const idxPasswordOrToken = userInfo.indexOf(':');
if (idxPasswordOrToken !== -1) {
res += percentEncode(userInfo.substr(0, idxPasswordOrToken), encoder[EncodePart.user]);
res += ':';
res += percentEncode(userInfo.substr(idxPasswordOrToken + 1), encoder[EncodePart.user]);
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);
} else {
res += percentEncode(userInfo, encoder[EncodePart.user]);
// <user>:<pass>@<auth>
res += encoder(userinfo.substr(0, idx), false);
res += ':';
res += encoder(userinfo.substr(idx + 1), false);
}
res += '@';
}
authority = authority.substr(idxUserInfo + 1).toLowerCase();
const idxPort = authority.indexOf(':');
if (idxPort !== -1) {
// <authority>:<port>
res += percentEncode(authority.substr(0, idxPort), encoder[EncodePart.authority]);
res += ':';
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);
}
res += percentEncode(authority.substr(idxPort + 1), encoder[EncodePart.authority]);
}
if (path) {
// encode the path
let pathEncoded = percentEncode(path, encoder[EncodePart.path]);
// lower-case windows drive letters in /C:/fff or C:/fff and escape `:`
let match = _driveLetterRegExp.exec(pathEncoded);
if (match) {
pathEncoded = match[1].toLowerCase() + '%3A' + pathEncoded.substr(match[0].length);
// 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
}
}
res += pathEncoded;
// encode the rest of the path
res += encoder(path, true);
}
if (query) {
res += '?';
res += percentEncode(query, encoder[EncodePart.query]);
res += encoder(query, false);
}
if (fragment) {
res += '#';
res += percentEncode(fragment, encoder[EncodePart.fragment]);
res += !skipEncoding ? encodeURIComponentFast(fragment, false) : fragment;
}
return res;
}
//#endregion
......@@ -2,11 +2,10 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { URI, UriComponents } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { pathToFileURL, fileURLToPath } from 'url';
suite('URI', () => {
test('file#toString', () => {
......@@ -64,8 +63,8 @@ suite('URI', () => {
assert.equal(URI.from({ scheme: 'http', authority: '', path: 'my/path' }).toString(), 'http:/my/path');
assert.equal(URI.from({ scheme: 'http', authority: '', path: '/my/path' }).toString(), 'http:/my/path');
//http://a-test-site.com/#test=true
assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: 'test=true' }).toString(), 'http://a-test-site.com/?test=true');
assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: '', fragment: 'test=true' }).toString(), 'http://a-test-site.com/#test=true');
assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: 'test=true' }).toString(), 'http://a-test-site.com/?test%3Dtrue');
assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: '', fragment: 'test=true' }).toString(), 'http://a-test-site.com/#test%3Dtrue');
});
test('http#toString, encode=FALSE', () => {
......@@ -88,7 +87,7 @@ suite('URI', () => {
assert.equal(uri2.fragment, uri3.fragment);
});
test('URI#with, identity', () => {
test('with, identity', () => {
let uri = URI.parse('foo:bar/path');
let uri2 = uri.with(null!);
......@@ -101,17 +100,17 @@ suite('URI', () => {
assert.ok(uri === uri2);
});
test('URI#with, changes', () => {
test('with, changes', () => {
assert.equal(URI.parse('before:some/file/path').with({ scheme: 'after' }).toString(), 'after:some/file/path');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(), 'http:/api/files/test.me?t=1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'http:/api/files/test.me?t=1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'https', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'https:/api/files/test.me?t=1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTP', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTP:/api/files/test.me?t=1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTPS', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTPS:/api/files/test.me?t=1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'boo', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'boo:/api/files/test.me?t=1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(), 'http:/api/files/test.me?t%3D1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'http:/api/files/test.me?t%3D1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'https', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'https:/api/files/test.me?t%3D1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTP', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTP:/api/files/test.me?t%3D1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTPS', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTPS:/api/files/test.me?t%3D1234');
assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'boo', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'boo:/api/files/test.me?t%3D1234');
});
test('URI#with, remove components #8465', () => {
test('with, remove components #8465', () => {
assert.equal(URI.parse('scheme://authority/path').with({ authority: '' }).toString(), 'scheme:/path');
assert.equal(URI.parse('scheme:/path').with({ authority: 'authority' }).with({ authority: '' }).toString(), 'scheme:/path');
assert.equal(URI.parse('scheme:/path').with({ authority: 'authority' }).with({ authority: null }).toString(), 'scheme:/path');
......@@ -121,7 +120,7 @@ suite('URI', () => {
assert.equal(URI.parse('scheme:/path').with({ authority: null }).toString(), 'scheme:/path');
});
test('URI#with, validation', () => {
test('with, validation', () => {
let uri = URI.parse('foo:bar/path');
assert.throws(() => uri.with({ scheme: 'fai:l' }));
assert.throws(() => uri.with({ scheme: 'fäil' }));
......@@ -235,11 +234,6 @@ suite('URI', () => {
assert.throws(() => URI.parse('file:////shares/files/p.cs'));
});
test('URI#parse, missing scheme', () => {
assert.throws(() => URI.parse('/foo/bar', true));
assertToString('/foo/bar', 'file:///foo/bar');
});
test('URI#file, win-speciale', () => {
if (isWindows) {
let value = URI.file('c:\\test\\drive');
......@@ -260,7 +254,7 @@ suite('URI', () => {
assert.equal(value.fsPath, '\\\\localhost\\c$\\GitDevelopment\\express');
assert.equal(value.query, '');
assert.equal(value.fragment, '');
assert.equal(value.toString(), 'file://localhost/c$/GitDevelopment/express');
assert.equal(value.toString(), 'file://localhost/c%24/GitDevelopment/express');
value = URI.file('c:\\test with %\\path');
assert.equal(value.path, '/c:/test with %/path');
......@@ -315,78 +309,50 @@ suite('URI', () => {
assert.equal(value.toString(), 'file:///a.file');
});
function assertToString(input: string | URI, expected: string) {
if (typeof input === 'string') {
input = URI.parse(input);
}
const actual = input.toString();
assert.equal(actual, expected.toString());
}
function assertComponents(input: string | URI, scheme: string, authority: string, path: string, query: string, fragment: string) {
if (typeof input === 'string') {
input = URI.parse(input);
}
assert.equal(input.scheme, scheme);
assert.equal(input.authority, authority);
assert.equal(input.path, path);
assert.equal(input.query, query);
assert.equal(input.fragment, fragment);
}
function assertEqualUri(input: string | URI, other: string | URI) {
if (typeof input === 'string') {
input = URI.parse(input);
}
if (typeof other === 'string') {
other = URI.parse(other);
}
assert.equal(input.scheme, other.scheme);
assert.equal(input.authority, other.authority);
assert.equal(input.path, other.path);
assert.equal(input.query, other.query);
assert.equal(input.fragment, other.fragment);
assert.equal(input.toString(), other.toString());
}
test('URI.toString, only scheme and query', () => {
assertToString('stuff:?qüery', 'stuff:?q%C3%BCery');
const value = URI.parse('stuff:?qüery');
assert.equal(value.toString(), 'stuff:?q%C3%BCery');
});
test('URI#toString, upper-case percent espaces', () => {
assertToString('file://sh%c3%a4res/path', 'file://sh%C3%A4res/path');
assertToString('file://sh%c3%z4res/path', 'file://sh%C3%z4res/path');
assertToString('file:///sh%a0res/path', 'file:///sh%A0res/path'); // also upper-cased invalid sequence
const value = URI.parse('file://sh%c3%a4res/path');
assert.equal(value.toString(), 'file://sh%C3%A4res/path');
});
test('URI#toString, lower-case windows drive letter', () => {
assertToString('untitled:c:/Users/jrieken/Code/abc.txt', 'untitled:c%3A/Users/jrieken/Code/abc.txt');
assertToString('untitled:C:/Users/jrieken/Code/abc.txt', 'untitled:c%3A/Users/jrieken/Code/abc.txt');
assert.equal(URI.parse('untitled:c:/Users/jrieken/Code/abc.txt').toString(), 'untitled:c%3A/Users/jrieken/Code/abc.txt');
assert.equal(URI.parse('untitled:C:/Users/jrieken/Code/abc.txt').toString(), 'untitled:c%3A/Users/jrieken/Code/abc.txt');
});
test('URI#toString, escape all the bits', () => {
const value = URI.file('/Users/jrieken/Code/_samples/18500/Mödel + Other Thîngß/model.js');
assertToString(value, 'file:///Users/jrieken/Code/_samples/18500/M%C3%B6del%20+%20Other%20Th%C3%AEng%C3%9F/model.js');
assert.equal(value.toString(), 'file:///Users/jrieken/Code/_samples/18500/M%C3%B6del%20%2B%20Other%20Th%C3%AEng%C3%9F/model.js');
});
test('URI#toString, don\'t encode port', () => {
let value = URI.parse('http://localhost:8080/far');
assertToString(value, 'http://localhost:8080/far');
assert.equal(value.toString(), 'http://localhost:8080/far');
value = URI.from({ scheme: 'http', authority: 'löcalhost:8080', path: '/far', query: undefined, fragment: undefined });
assertToString(value, 'http://l%C3%B6calhost:8080/far');
assert.equal(value.toString(), 'http://l%C3%B6calhost:8080/far');
});
test('URI#toString, user information in authority', () => {
assertToString('http://foo:bar@localhost/far', 'http://foo:bar@localhost/far');
assertToString('http://foo@localhost/far', 'http://foo@localhost/far');
assertToString('http://foo:bAr@localhost:8080/far', 'http://foo:bAr@localhost:8080/far');
assertToString('http://foo@localhost:8080/far', 'http://foo@localhost:8080/far');
assertToString(
URI.from({ scheme: 'http', authority: 'föö:bör@löcalhost:8080', path: '/far', query: undefined, fragment: undefined }),
'http://f%C3%B6%C3%B6:b%C3%B6r@l%C3%B6calhost:8080/far'
);
let value = URI.parse('http://foo:bar@localhost/far');
assert.equal(value.toString(), 'http://foo:bar@localhost/far');
value = URI.parse('http://foo@localhost/far');
assert.equal(value.toString(), 'http://foo@localhost/far');
value = URI.parse('http://foo:bAr@localhost:8080/far');
assert.equal(value.toString(), 'http://foo:bAr@localhost:8080/far');
value = URI.parse('http://foo@localhost:8080/far');
assert.equal(value.toString(), 'http://foo@localhost:8080/far');
value = URI.from({ scheme: 'http', authority: 'föö:bör@löcalhost:8080', path: '/far', query: undefined, fragment: undefined });
assert.equal(value.toString(), 'http://f%C3%B6%C3%B6:b%C3%B6r@l%C3%B6calhost:8080/far');
});
test('correctFileUriToFilePath2', () => {
......@@ -410,7 +376,7 @@ suite('URI', () => {
let uri = URI.parse('https://go.microsoft.com/fwlink/?LinkId=518008');
assert.equal(uri.query, 'LinkId=518008');
assert.equal(uri.toString(true), 'https://go.microsoft.com/fwlink/?LinkId=518008');
assert.equal(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId=518008');
assert.equal(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId%3D518008');
let uri2 = URI.parse(uri.toString());
assert.equal(uri2.query, 'LinkId=518008');
......@@ -419,7 +385,7 @@ suite('URI', () => {
uri = URI.parse('https://go.microsoft.com/fwlink/?LinkId=518008&foö&ké¥=üü');
assert.equal(uri.query, 'LinkId=518008&foö&ké¥=üü');
assert.equal(uri.toString(true), 'https://go.microsoft.com/fwlink/?LinkId=518008&foö&ké¥=üü');
assert.equal(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId=518008&fo%C3%B6&k%C3%A9%C2%A5=%C3%BC%C3%BC');
assert.equal(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId%3D518008%26fo%C3%B6%26k%C3%A9%C2%A5%3D%C3%BC%C3%BC');
uri2 = URI.parse(uri.toString());
assert.equal(uri2.query, 'LinkId=518008&foö&ké¥=üü');
......@@ -461,122 +427,47 @@ suite('URI', () => {
});
test('Unable to open \'%A0.txt\': URI malformed #76506', function () {
let uriFromPath = URI.file('/foo/%A0.txt');
let uriFromStr = URI.parse(uriFromPath.toString());
assert.equal(uriFromPath.scheme, uriFromStr.scheme);
assert.equal(uriFromPath.path, uriFromStr.path);
assert.equal(uriFromPath.toString(), 'file:///foo/%25A0.txt');
assert.equal(uriFromStr.toString(), 'file:///foo/%25A0.txt');
});
test('Unable to open \'%2e.txt\'', function () {
let uriFromPath = URI.file('/foo/%2e.txt');
let uriFromStr = URI.parse(uriFromPath.toString());
assert.equal(uriFromPath.scheme, uriFromStr.scheme);
assert.equal(uriFromPath.path, uriFromStr.path);
assert.equal(uriFromPath.toString(), 'file:///foo/%252e.txt');
assert.equal(uriFromStr.toString(), 'file:///foo/%252e.txt');
let uri = URI.file('/foo/%A0.txt');
let uri2 = URI.parse(uri.toString());
assert.equal(uri.scheme, uri2.scheme);
assert.equal(uri.path, uri2.path);
uri = URI.file('/foo/%2e.txt');
uri2 = URI.parse(uri.toString());
assert.equal(uri.scheme, uri2.scheme);
assert.equal(uri.path, uri2.path);
});
test('Links in markdown are broken if url contains encoded parameters #79474', function () {
this.skip();
let strIn = 'https://myhost.com/Redirect?url=http%3A%2F%2Fwww.bing.com%3Fsearch%3Dtom';
let uri1 = URI.parse(strIn);
assertToString(uri1, strIn);
let strOut = uri1.toString();
let uri2 = URI.parse(strOut);
assertEqualUri(uri1, uri2);
assert.equal(strIn, strOut);
assert.equal(uri1.scheme, uri2.scheme);
assert.equal(uri1.authority, uri2.authority);
assert.equal(uri1.path, uri2.path);
assert.equal(uri1.query, uri2.query);
assert.equal(uri1.fragment, uri2.fragment);
assert.equal(strIn, strOut); // fails here!!
});
test('Uri#parse can break path-component #45515', function () {
this.skip();
let strIn = 'https://firebasestorage.googleapis.com/v0/b/brewlangerie.appspot.com/o/products%2FzVNZkudXJyq8bPGTXUxx%2FBetterave-Sesame.jpg?alt=media&token=0b2310c4-3ea6-4207-bbde-9c3710ba0437';
let uri1 = URI.parse(strIn);
assertToString(uri1, strIn);
assertComponents(uri1,
'https',
'firebasestorage.googleapis.com',
'/v0/b/brewlangerie.appspot.com/o/products/zVNZkudXJyq8bPGTXUxx/Betterave-Sesame.jpg', // INCORRECT: %2F got decoded but for compat reasons we cannot change this anymore...
'alt=media&token=0b2310c4-3ea6-4207-bbde-9c3710ba0437',
''
);
let strOut = uri1.toString();
let uri2 = URI.parse(strOut);
assertEqualUri(uri1, uri2);
});
test('Nonce does not match on login #75755', function () {
let uri = URI.parse('http://localhost:60371/signin?nonce=iiK1zRI%2BHyDCKb2zatvrYA%3D%3D');
assertComponents(uri, 'http', 'localhost:60371', '/signin', 'nonce=iiK1zRI+HyDCKb2zatvrYA==', '');
assertToString(uri, 'http://localhost:60371/signin?nonce=iiK1zRI%2BHyDCKb2zatvrYA%3D%3D');
});
test('URI.parse() failes with `Cannot read property \'toLowerCase\' of undefined` #75344', function () {
try {
URI.parse('abc');
assert.ok(false);
} catch (e) {
assert.ok(e instanceof Error && e.message.indexOf('[UriError]:'));
}
});
test('vscode.Uri.parse is double encoding certain characters', function () {
const inStr = 'https://github.com/PowerShell/vscode-powershell#reporting-problems';
assertToString(inStr, inStr);
assertComponents(inStr, 'https', 'github.com', '/PowerShell/vscode-powershell', '', 'reporting-problems');
});
test('Symbols in URL fragment should not be encoded #76635', function () {
const inStr = 'http://source.roslyn.io/#Microsoft.CodeAnalysis.CSharp/CSharpCompilationOptions.cs,20';
assertToString(inStr, inStr);
assertComponents(inStr, 'http', 'source.roslyn.io', '/', '', 'Microsoft.CodeAnalysis.CSharp/CSharpCompilationOptions.cs,20');
});
test('vscode.env.openExternal is not working correctly because of unnecessary escape processing. #76606', function () {
const inStr = 'x-github-client://openRepo/https://github.com/wraith13/open-in-github-desktop-vscode.git';
assertToString(inStr, 'x-github-client://openrepo/https://github.com/wraith13/open-in-github-desktop-vscode.git'); // lower-cased authory
assertComponents(inStr, 'x-github-client', 'openRepo', '/https://github.com/wraith13/open-in-github-desktop-vscode.git', '', '');
});
test('When I click on a link in the terminal, browser opens with a URL which seems to be the link, but run through decodeURIComponent #52211', function () {
const inStr = 'http://localhost:8448/#/repository?path=%2Fhome%2Fcapaj%2Fgit_projects%2Fartillery';
assertToString(inStr, inStr);
assertComponents(inStr, 'http', 'localhost:8448', '/', '', '/repository?path=/home/capaj/git_projects/artillery'); // INCORRECT %2F lost
});
test('Terminal breaks weblink for fish shell #44278', function () {
const inStr = 'https://eu-west-1.console.aws.amazon.com/cloudformation/home\\?region=eu-west-1#/stacks\\?filter=active';
assertToString(inStr, inStr);
assertComponents(inStr, 'https', 'eu-west-1.console.aws.amazon.com', '/cloudformation/home\\', 'region=eu-west-1', '/stacks\\?filter=active');
});
test('Markdown mode cannot open links that contains some codes by percent-encoding. #32026', function () {
const inStr = 'https://www.google.co.jp/search?q=%91%E5';
assertToString(inStr, inStr);
assertComponents(inStr, 'https', 'www.google.co.jp', '/search', 'q=%91%E5', '');
});
test('URI#parse creates normalized output', function () {
function assertToString(input: string, output: string = input): void {
const uri = URI.parse(input);
assert.equal(uri.toString(), output);
}
// don't break query string, encoded characters
assertToString('https://firebasestorage.googleapis.com/v0/b/brewlangerie.appspot.com/o/products%2FzVNZkudXJyq8bPGTXUxx%2FBetterave-Sesame.jpg?alt=media&token=0b2310c4-3ea6-4207-bbde-9c3710ba0437');
assertToString('https://go.microsoft.com/fwlink/?LinkId=518008');
assertToString('https://twitter.com/search?src=typd&q=%23tag');
assertToString('http://localhost:3000/#/foo?bar=baz');
assertToString('https://myhost.com/Redirect?url=http%3A%2F%2Fwww.bing.com%3Fsearch%3Dtom');
assertToString('https://myhost.com/Redirect?url=http%3a%2f%2Fwww.bing.com%3Fsearch%3Dtom', 'https://myhost.com/Redirect?url=http%3A%2F%2Fwww.bing.com%3Fsearch%3Dtom'); // upper-case hex
assertToString('https://go.microsoft.com/fwlink/?LinkId=518008&foö&ké¥=üü', 'https://go.microsoft.com/fwlink/?LinkId=518008&fo%C3%B6&k%C3%A9%C2%A5=%C3%BC%C3%BC'); // encode umlaute and friends
// normalize things like
assertToString('file:///c:/test/me', 'file:///c%3A/test/me'); // drive letter treatment
assertToString('file:///C:/test/me', 'file:///c%3A/test/me');
assertToString('file:///c%3A/test/me', 'file:///c%3A/test/me');
assertToString('file:///C%3A/test/me', 'file:///c%3A/test/me');
assert.equal(uri1.scheme, uri2.scheme);
assert.equal(uri1.authority, uri2.authority);
assert.equal(uri1.path, uri2.path);
assert.equal(uri1.query, uri2.query);
assert.equal(uri1.fragment, uri2.fragment);
assert.equal(strIn, strOut); // fails here!!
});
test('URI - (de)serialize', function () {
......@@ -608,142 +499,4 @@ suite('URI', () => {
// }
// console.profileEnd();
});
// ------ check against standard URL and nodejs-file-url utils
function assertUriFromFsPath(path: string, recurse = true): void {
let actual = URI.file(path).toString();
if (isWindows) {
// we always encode windows drive letters and since nodejs
// never does. to compare we need to undo our encoding...
actual = actual.replace(/(\/[a-z])%3A/, '$1:');
}
let expected = pathToFileURL(path).href;
assert.equal(actual, expected, path);
if (recurse) {
assertFsPathFromUri(expected, false);
}
}
function assertFsPathFromUri(uri: string, recurse = true): void {
let actual = URI.parse(uri).fsPath;
let expected = fileURLToPath(uri);
assert.equal(actual, expected, uri);
if (recurse) {
assertUriFromFsPath(actual, false);
}
}
test('URI.file and pathToFileURL', function () {
// nodejs is strict to the platform on which it runs
if (isWindows) {
assertUriFromFsPath('d:/foo/bar');
assertUriFromFsPath('d:/foo/%2e.txt');
assertUriFromFsPath('d:/foo/%A0.txt');
assertUriFromFsPath('d:/foo/ü.txt');
assertUriFromFsPath('d:/foo/ß.txt');
assertUriFromFsPath('d:/my/c#project/d.cs');
assertUriFromFsPath('c:\\win\\path');
assertUriFromFsPath('c:\\test\\drive');
assertUriFromFsPath('c:\\test with %\\path');
assertUriFromFsPath('c:\\test with %25\\path');
assertUriFromFsPath('c:\\test with %25\\c#code');
// assertUriFromFsPath('\\\\shäres\\path\\c#\\plugin.json'); // nodejs doesn't accept UNC paths as paths
// assertUriFromFsPath('\\\\localhost\\c$\\GitDevelopment\\express');
// assertUriFromFsPath('\\\\shares');
// assertUriFromFsPath('\\\\shares\\');
} else {
assertUriFromFsPath('/foo/bar');
assertUriFromFsPath('/foo/%2e.txt');
assertUriFromFsPath('/foo/%A0.txt');
assertUriFromFsPath('/foo/ü.txt');
assertUriFromFsPath('/foo/ß.txt');
assertUriFromFsPath('/my/c#project/d.cs');
assertUriFromFsPath('/c\\win\\path');
}
});
test('URI.fsPath and fileURLToPath', function () {
// nodejs is strict to the platform on which it runs
if (isWindows) {
assertFsPathFromUri('file:///f:/foo/bar');
assertFsPathFromUri('file:///f:/fo%25/bar');
assertFsPathFromUri('file:///f:/foo/b ar/text.cs');
assertFsPathFromUri('file:///f:/foö/bar');
assertFsPathFromUri('file:///f:/fo%C3%B6/bar');
assertFsPathFromUri('file:///f:/');
assertFsPathFromUri('file:///f:/my/c%23project/c.cs');
assertFsPathFromUri('file:///c:/bar/foo');
assertFsPathFromUri('file:///c:/alex.txt');
assertFsPathFromUri('file:///c:/Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins');
assertFsPathFromUri('file://unc-host/foö/bar', false);
// assertFsPathFromUri('file://unc-host/', false); //nodejs \\unc-host\ vs code \
assertFsPathFromUri('file://monacotools/folder/isi.txt', false);
assertFsPathFromUri('file://monacotools1/certificates/SSL/', false);
} else {
assertFsPathFromUri('file:///foo/bar');
assertFsPathFromUri('file:///fo%25/bar');
assertFsPathFromUri('file:///foo/b ar/text.cs');
assertFsPathFromUri('file:///foö/bar');
assertFsPathFromUri('file:///fo%C3%B6/bar');
assertFsPathFromUri('file:///');
assertFsPathFromUri('file:///my/c%23project/c.cs');
assertFsPathFromUri('file:///foo%5cbar');
assertFsPathFromUri('file:///foo%5Cbar');
assertFsPathFromUri('file:///foo%5C%5cbar');
assertFsPathFromUri('file:///Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins');
}
});
// ---- check against standard url
test('URI.toString equals (whatwg) URL.toString', function () {
function assertToString(uri: string): void {
const actual = URI.parse(uri).toString();
const expected = new URL(uri).href;
assert.equal(actual, expected);
}
assertToString('before:some/file/path');
assertToString('scheme://authority/path');
assertToString('scheme:/path');
assertToString('foo:bar/path');
// assertToString('http:/api/files/test.me?t=1234'); // URL makes api the hostname,
assertToString('http://api/files/test.me?t=1234');
// assertToString('file:///c:/test/me'); // we encode the colon
// assertToString('file:///c:/Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins/c%23/plugin.json');
// assertToString('file:///c:/test %25/path');
assertToString('file://shares/files/c%23/p.cs');
assertToString('inmemory:');
assertToString('foo:api/files/test');
assertToString('f3ile:?q');
assertToString('f3ile:#d');
assertToString('foo+bar:path');
assertToString('foo-bar:path');
assertToString('foo.bar:path');
assertToString('file:///_:/path');
assertToString('https://firebasestorage.googleapis.com/v0/b/brewlangerie.appspot.com/o/products%2FzVNZkudXJyq8bPGTXUxx%2FBetterave-Sesame.jpg?alt=media&token=0b2310c4-3ea6-4207-bbde-9c3710ba0437');
assertToString('https://myhost.com/Redirect?url=http%3A%2F%2Fwww.bing.com%3Fsearch%3Dtom');
assertToString('debug:internalModule.js?session=aDebugSessionId&ref=11');
assertToString('debug:internalModule.js?session%3DaDebugSessionId%26ref%3D11');
assertToString('https://github.com/microsoft/vscode/issues/33746#issuecomment-545345356');
assertToString('http://localhost:3000/#/foo?bar=baz');
assertToString('https://myhost.com/Redirect?url=http%3A%2F%2Fwww.bing.com%3Fsearch%3Dtom');
assertToString('https://myhost.com/my/pãth/ìß/hē®ę');
assertToString('http://foo:bar@localhost/far');
assertToString('http://foo@localhost/far');
assertToString('http://foo:bAr@localhost:8080/far');
assertToString('http://foo@localhost:8080/far');
assertToString('http://localhost:60371/signin?nonce=iiK1zRI%2BHyDCKb2zatvrYA%3D%3D');
assertToString('https://github.com/PowerShell/vscode-powershell#reporting-problems');
assertToString('http://source.roslyn.io/#Microsoft.CodeAnalysis.CSharp/CSharpCompilationOptions.cs,20');
// assertToString('x-github-client://openRepo/https://github.com/wraith13/open-in-github-desktop-vscode.git'); we lower-case
assertToString('x-github-client://openrepo/https://github.com/wraith13/open-in-github-desktop-vscode.git');
assertToString('http://www.google.com/?parameter1=\'http://imageserver.domain.com/?parameter2=1\'');
assertToString('http://some.ws/page?id=123&select=%22quoted_string%22');
// assertToString('https://eu-west-1.console.aws.amazon.com/cloudformation/home\\?region=eu-west-1#/stacks\\?filter=active'); URL makes slash out of backslash
assertToString('http://localhost/?user=test%2B1@example.com');
assertToString('https://www.google.co.jp/search?q=%91%E5');
});
});
......@@ -149,7 +149,7 @@ export class OpenerService extends Disposable implements IOpenerService {
private async _doOpenExternal(resource: URI, options: OpenOptions | undefined): Promise<boolean> {
const { resolved } = await this.resolveExternalUri(resource, options);
dom.windowOpenNoOpener(resolved.toString());
dom.windowOpenNoOpener(encodeURI(resolved.toString(true)));
return true;
}
......
......@@ -129,11 +129,9 @@ declare namespace monaco {
* Creates a new Uri from a string, e.g. `http://www.msft.com/some/path`,
* `file:///usr/home`, or `scheme:with/path`.
*
* *Note:* When the input lacks a scheme then `file` is used.
*
* @param value A string which represents an Uri (see `Uri#toString`).
*/
static parse(value: string, strict?: boolean): Uri;
static parse(value: string): Uri;
/**
* Creates a new Uri from a file system path, e.g. `c:\my\files`,
* `/usr/home`, or `\\server\share\some\path`.
......
......@@ -1236,8 +1236,8 @@ declare module 'vscode' {
* `file:///usr/home`, or `scheme:with/path`.
*
* *Note* that for a while uris without a `scheme` were accepted. That is not correct
* as all uris should have a scheme. When missing the `file`-scheme is being used unless
* the `strict`-argument is `true` in which case an error is thrown.
* as all uris should have a scheme. To avoid breakage of existing code the optional
* `strict`-argument has been added. We *strongly* advise to use it, e.g. `Uri.parse('my:uri', true)`
*
* @see [Uri.toString](#Uri.toString)
* @param value The string value of an Uri.
......
......@@ -260,7 +260,7 @@ export namespace MarkdownString {
const collectUri = (href: string): string => {
try {
let uri = URI.parse(href, true);
let uri = URI.parse(href);
uri = uri.with({ query: _uriMassage(uri.query, resUris) });
resUris[href] = uri;
} catch (e) {
......
......@@ -36,7 +36,7 @@ suite('Debug - Source', () => {
assert.equal(source.name, 'internalModule.js');
assert.equal(source.inMemory, true);
assert.equal(source.reference, 11);
assert.equal(source.uri.toString(), 'debug:internalModule.js?session=aDebugSessionId&ref=11');
assert.equal(source.uri.toString(), 'debug:internalModule.js?session%3DaDebugSessionId%26ref%3D11');
});
test('get encoded debug data', () => {
......
......@@ -37,16 +37,16 @@ export class WebviewPortMappingManager extends Disposable {
if (tunnel.tunnelLocalPort === mapping.webviewPort) {
return undefined;
}
return uri.with({
return encodeURI(uri.with({
authority: `127.0.0.1:${tunnel.tunnelLocalPort}`,
}).toString();
}).toString(true));
}
}
if (mapping.webviewPort !== mapping.extensionHostPort) {
return uri.with({
return encodeURI(uri.with({
authority: `${requestLocalHostInfo.address}:${mapping.extensionHostPort}`
}).toString();
}).toString(true));
}
}
}
......
......@@ -408,7 +408,7 @@ export class ElectronWindow extends Disposable {
// the main process to prevent window focus issues.
if (this.shouldOpenExternal(resource, options)) {
const { resolved } = await this.openerService.resolveExternalUri(resource, options);
const success = await this.electronService.openExternal(resolved.toString());
const success = await this.electronService.openExternal(encodeURI(resolved.toString(true)));
if (!success && resolved.scheme === Schemas.file) {
// if opening failed, and this is a file, we can still try to reveal it
await this.electronService.showItemInFolder(resolved.fsPath);
......
......@@ -72,17 +72,6 @@ suite('RipgrepTextSearchEngine', () => {
suite('RipgrepParser', () => {
const TEST_FOLDER = URI.file('/foo/bar');
function joinPathExt(uri: URI, path: string): URI {
const result = joinPath(uri, path);
result.toString();
// ^^^^^^^^
// doing this to init the URI._formatted-field because this
// test compares the output of URI.toJSON and because
// calling URI.file (called by RipgrepParser) will also
// initialize URI._formatted
return result;
}
function testParser(inputData: string[], expectedResults: TextSearchResult[]): void {
const testParser = new RipgrepParser(1000, TEST_FOLDER.fsPath);
......@@ -130,7 +119,7 @@ suite('RipgrepTextSearchEngine', () => {
text: 'foobar',
matches: [new Range(0, 3, 0, 6)]
},
uri: joinPathExt(TEST_FOLDER, 'file1.js'),
uri: joinPath(TEST_FOLDER, 'file1.js'),
ranges: [new Range(3, 3, 3, 6)]
}
]);
......@@ -149,7 +138,7 @@ suite('RipgrepTextSearchEngine', () => {
text: 'foobar',
matches: [new Range(0, 3, 0, 6)]
},
uri: joinPathExt(TEST_FOLDER, 'file1.js'),
uri: joinPath(TEST_FOLDER, 'file1.js'),
ranges: [new Range(3, 3, 3, 6)]
},
{
......@@ -157,7 +146,7 @@ suite('RipgrepTextSearchEngine', () => {
text: 'foobar',
matches: [new Range(0, 3, 0, 6)]
},
uri: joinPathExt(TEST_FOLDER, 'app/file2.js'),
uri: joinPath(TEST_FOLDER, 'app/file2.js'),
ranges: [new Range(3, 3, 3, 6)]
},
{
......@@ -165,7 +154,7 @@ suite('RipgrepTextSearchEngine', () => {
text: 'foobar',
matches: [new Range(0, 3, 0, 6)]
},
uri: joinPathExt(TEST_FOLDER, 'app2/file3.js'),
uri: joinPath(TEST_FOLDER, 'app2/file3.js'),
ranges: [new Range(3, 3, 3, 6)]
}
]);
......@@ -194,7 +183,7 @@ suite('RipgrepTextSearchEngine', () => {
text: 'foo bar',
matches: [new Range(0, 3, 0, 7)]
},
uri: joinPathExt(TEST_FOLDER, 'file1.js'),
uri: joinPath(TEST_FOLDER, 'file1.js'),
ranges: [new Range(3, 3, 3, 7)]
},
{
......@@ -202,7 +191,7 @@ suite('RipgrepTextSearchEngine', () => {
text: 'foobar',
matches: [new Range(0, 3, 0, 6)]
},
uri: joinPathExt(TEST_FOLDER, 'app/file2.js'),
uri: joinPath(TEST_FOLDER, 'app/file2.js'),
ranges: [new Range(3, 3, 3, 6)]
},
{
......@@ -210,7 +199,7 @@ suite('RipgrepTextSearchEngine', () => {
text: 'foobar',
matches: [new Range(0, 3, 0, 6)]
},
uri: joinPathExt(TEST_FOLDER, 'app2/file3.js'),
uri: joinPath(TEST_FOLDER, 'app2/file3.js'),
ranges: [new Range(3, 3, 3, 6)]
}
]);
......
......@@ -22,11 +22,13 @@ suite('ExtHostTypeConverter', function () {
data = MarkdownString.from('Hello [link](foo)');
assert.equal(data.value, 'Hello [link](foo)');
assert.equal(size(data.uris!), 0);
assert.equal(size(data.uris!), 1);
assert.ok(!!data.uris!['foo']);
data = MarkdownString.from('Hello [link](www.noscheme.bad)');
assert.equal(data.value, 'Hello [link](www.noscheme.bad)');
assert.equal(size(data.uris!), 0);
assert.equal(size(data.uris!), 1);
assert.ok(!!data.uris!['www.noscheme.bad']);
data = MarkdownString.from('Hello [link](foo:path)');
assert.equal(data.value, 'Hello [link](foo:path)');
......
......@@ -22,8 +22,7 @@ suite('ExtHostTypes', function () {
assert.deepEqual(uri.toJSON(), {
$mid: 1,
scheme: 'file',
path: '/path/test.file',
external: 'file:///path/test.file'
path: '/path/test.file'
});
assert.ok(uri.fsPath);
......@@ -31,7 +30,6 @@ suite('ExtHostTypes', function () {
$mid: 1,
scheme: 'file',
path: '/path/test.file',
external: 'file:///path/test.file',
fsPath: '/path/test.file'.replace(/\//g, isWindows ? '\\' : '/'),
_sep: isWindows ? 1 : undefined,
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册