mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-25 11:08:51 +01:00
[json] language server & client as extension
This commit is contained in:
165
extensions/json/server/src/utils/httpRequest.ts
Normal file
165
extensions/json/server/src/utils/httpRequest.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Url, parse as parseUrl } from 'url';
|
||||
import { getProxyAgent } from './proxy';
|
||||
import https = require('https');
|
||||
import http = require('http');
|
||||
import nls = require('./nls');
|
||||
|
||||
export interface IXHROptions {
|
||||
type?: string;
|
||||
url?: string;
|
||||
user?: string;
|
||||
password?: string;
|
||||
headers?: any;
|
||||
timeout?: number;
|
||||
data?: any;
|
||||
agent?: any;
|
||||
strictSSL?: boolean;
|
||||
responseType?: string;
|
||||
followRedirects: number;
|
||||
}
|
||||
|
||||
export interface IXHRResponse {
|
||||
responseText: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
let proxyUrl: string = null;
|
||||
let strictSSL: boolean = true;
|
||||
|
||||
function assign(destination: any, ...sources: any[]): any {
|
||||
sources.forEach(source => Object.keys(source).forEach((key) => destination[key] = source[key]));
|
||||
return destination;
|
||||
}
|
||||
|
||||
export function configure(_proxyUrl: string, _strictSSL: boolean): void {
|
||||
proxyUrl = _proxyUrl;
|
||||
strictSSL = _strictSSL;
|
||||
}
|
||||
|
||||
export function xhr(options: IXHROptions): Promise<IXHRResponse> {
|
||||
const agent = getProxyAgent(options.url, { proxyUrl, strictSSL });
|
||||
options = assign({}, options);
|
||||
options = assign(options, { agent, strictSSL });
|
||||
|
||||
return request(options).then(result => new Promise<IXHRResponse>((c, e) => {
|
||||
let res = result.res;
|
||||
let data: string[] = [];
|
||||
res.on('data', c => data.push(c));
|
||||
res.on('end', () => {
|
||||
if (options.followRedirects > 0 && (res.statusCode >= 300 && res.statusCode <= 303 || res.statusCode === 307)) {
|
||||
let location = res.headers['location'];
|
||||
if (location) {
|
||||
let newOptions = {
|
||||
type: options.type, url: location, user: options.user, password: options.password, responseType: options.responseType, headers: options.headers,
|
||||
timeout: options.timeout, followRedirects: options.followRedirects - 1, data: options.data
|
||||
};
|
||||
xhr(newOptions).then(c, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let response: IXHRResponse = {
|
||||
responseText: data.join(''),
|
||||
status: res.statusCode
|
||||
};
|
||||
|
||||
if ((res.statusCode >= 200 && res.statusCode < 300) || res.statusCode === 1223) {
|
||||
c(response);
|
||||
} else {
|
||||
e(response);
|
||||
}
|
||||
});
|
||||
}), err => {
|
||||
let message: string;
|
||||
|
||||
if (agent) {
|
||||
message = 'Unable to to connect to ' + options.url + ' through a proxy . Error: ' + err.message;
|
||||
} else {
|
||||
message = 'Unable to to connect to ' + options.url + '. Error: ' + err.message;
|
||||
}
|
||||
|
||||
return Promise.reject<IXHRResponse>({
|
||||
responseText: message,
|
||||
status: 404
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface IRequestResult {
|
||||
req: http.ClientRequest;
|
||||
res: http.ClientResponse;
|
||||
}
|
||||
|
||||
function request(options: IXHROptions): Promise<IRequestResult> {
|
||||
let req: http.ClientRequest;
|
||||
|
||||
return new Promise<IRequestResult>((c, e) => {
|
||||
let endpoint = parseUrl(options.url);
|
||||
|
||||
let opts: https.RequestOptions = {
|
||||
hostname: endpoint.hostname,
|
||||
port: endpoint.port ? parseInt(endpoint.port) : (endpoint.protocol === 'https:' ? 443 : 80),
|
||||
path: endpoint.path,
|
||||
method: options.type || 'GET',
|
||||
headers: options.headers,
|
||||
agent: options.agent,
|
||||
rejectUnauthorized: (typeof options.strictSSL === 'boolean') ? options.strictSSL : true
|
||||
};
|
||||
|
||||
if (options.user && options.password) {
|
||||
opts.auth = options.user + ':' + options.password;
|
||||
}
|
||||
|
||||
let protocol = endpoint.protocol === 'https:' ? https : http;
|
||||
req = protocol.request(opts, (res: http.ClientResponse) => {
|
||||
if (res.statusCode >= 300 && res.statusCode < 400 && options.followRedirects && options.followRedirects > 0 && res.headers['location']) {
|
||||
c(<any> request(assign({}, options, {
|
||||
url: res.headers['location'],
|
||||
followRedirects: options.followRedirects - 1
|
||||
})));
|
||||
} else {
|
||||
c({ req, res });
|
||||
}
|
||||
});
|
||||
req.on('error', e);
|
||||
|
||||
options.timeout && req.setTimeout(options.timeout);
|
||||
options.data && req.write(options.data);
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
export function getErrorStatusDescription(status: number) : string {
|
||||
if (status < 400) {
|
||||
return void 0;
|
||||
}
|
||||
switch (status) {
|
||||
case 400: return nls.localize('status.400', 'Bad request. The request cannot be fulfilled due to bad syntax.');
|
||||
case 401: return nls.localize('status.401', 'Unauthorized. The server is refusing to respond.');
|
||||
case 403: return nls.localize('status.403', 'Forbidden. The server is refusing to respond.');
|
||||
case 404: return nls.localize('status.404', 'Not Found. The requested location could not be found.');
|
||||
case 405: return nls.localize('status.405', 'Method not allowed. A request was made using a request method not supported by that location.');
|
||||
case 406: return nls.localize('status.406', 'Not Acceptable. The server can only generate a response that is not accepted by the client.');
|
||||
case 407: return nls.localize('status.407', 'Proxy Authentication Required. The client must first authenticate itself with the proxy.');
|
||||
case 408: return nls.localize('status.408', 'Request Timeout. The server timed out waiting for the request.');
|
||||
case 409: return nls.localize('status.409', 'Conflict. The request could not be completed because of a conflict in the request.');
|
||||
case 410: return nls.localize('status.410', 'Gone. The requested page is no longer available.');
|
||||
case 411: return nls.localize('status.411', 'Length Required. The "Content-Length" is not defined.');
|
||||
case 412: return nls.localize('status.412', 'Precondition Failed. The precondition given in the request evaluated to false by the server.');
|
||||
case 413: return nls.localize('status.413', 'Request Entity Too Large. The server will not accept the request, because the request entity is too large.');
|
||||
case 414: return nls.localize('status.414', 'Request-URI Too Long. The server will not accept the request, because the URL is too long.');
|
||||
case 415: return nls.localize('status.415', 'Unsupported Media Type. The server will not accept the request, because the media type is not supported.');
|
||||
case 500: return nls.localize('status.500', 'Internal Server Error.');
|
||||
case 501: return nls.localize('status.501', 'Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request.');
|
||||
case 503: return nls.localize('status.503', 'Service Unavailable. The server is currently unavailable (overloaded or down).');
|
||||
default: return nls.localize('status.416', 'HTTP status code {0}', status);
|
||||
}
|
||||
}
|
||||
88
extensions/json/server/src/utils/lines.ts
Normal file
88
extensions/json/server/src/utils/lines.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
Position
|
||||
} from 'vscode-languageserver';
|
||||
|
||||
export interface LinesModel {
|
||||
/**
|
||||
* Converts a zero-based offset to a position.
|
||||
*
|
||||
* @param offset A zero-based offset.
|
||||
* @return A valid [position](#Position).
|
||||
*/
|
||||
positionAt(offset: number): Position;
|
||||
|
||||
/**
|
||||
* Converts the position to a zero-based offset.
|
||||
*
|
||||
* The position will be [adjusted](#TextDocument.validatePosition).
|
||||
*
|
||||
* @param position A position.
|
||||
* @return A valid zero-based offset.
|
||||
*/
|
||||
offsetAt(position: Position): number;
|
||||
|
||||
/**
|
||||
* The number of lines in this document.
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
lineCount: number;
|
||||
}
|
||||
|
||||
export function create(text:string) : LinesModel {
|
||||
const lineStarts: number[] = [];
|
||||
var isLineStart = true;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if (isLineStart) {
|
||||
lineStarts.push(i);
|
||||
isLineStart = false;
|
||||
}
|
||||
var ch = text.charAt(i);
|
||||
isLineStart = (ch === '\r' || ch === '\n');
|
||||
if (ch === '\r' && i + 1 < text.length && text.charAt(i+1) === '\n') {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (isLineStart && text.length > 0) {
|
||||
lineStarts.push(text.length);
|
||||
}
|
||||
return {
|
||||
positionAt: (offset:number) => {
|
||||
offset = Math.max(Math.min(offset, text.length), 0);
|
||||
let low = 0, high = lineStarts.length;
|
||||
if (high === 0) {
|
||||
return Position.create(0, offset);
|
||||
}
|
||||
while (low < high) {
|
||||
let mid = Math.floor((low + high) / 2);
|
||||
if (lineStarts[mid] > offset) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
// low is the least x for which the line offset is larger than the offset
|
||||
// or array.length if no element fullfills the given function.
|
||||
var line = low - 1;
|
||||
return Position.create(line, offset - lineStarts[line]);
|
||||
},
|
||||
offsetAt: (position: Position) => {
|
||||
if (position.line >= lineStarts.length) {
|
||||
return text.length;
|
||||
} else if (position.line < 0) {
|
||||
return 0;
|
||||
}
|
||||
var lineStart = lineStarts[position.line];
|
||||
var nextLineStart = (position.line + 1 < lineStarts.length) ? lineStarts[position.line + 1] : text.length;
|
||||
return Math.max(Math.min(lineStart + position.character, nextLineStart), lineStart);
|
||||
},
|
||||
lineCount: lineStarts.length
|
||||
}
|
||||
|
||||
}
|
||||
15
extensions/json/server/src/utils/nls.ts
Normal file
15
extensions/json/server/src/utils/nls.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export function localize(key: string, message: string, ...formatArgs: any[]) {
|
||||
if (formatArgs.length > 0) {
|
||||
return message.replace(/\{(\d+)\}/g, function(match, rest) {
|
||||
var index = rest[0];
|
||||
return typeof formatArgs[index] !== 'undefined' ? formatArgs[index] : match;
|
||||
});
|
||||
}
|
||||
return message;
|
||||
}
|
||||
49
extensions/json/server/src/utils/proxy.ts
Normal file
49
extensions/json/server/src/utils/proxy.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Url, parse as parseUrl } from 'url';
|
||||
import HttpProxyAgent = require('http-proxy-agent');
|
||||
import HttpsProxyAgent = require('https-proxy-agent');
|
||||
|
||||
function getSystemProxyURI(requestURL: Url): string {
|
||||
if (requestURL.protocol === 'http:') {
|
||||
return process.env.HTTP_PROXY || process.env.http_proxy || null;
|
||||
} else if (requestURL.protocol === 'https:') {
|
||||
return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface IOptions {
|
||||
proxyUrl?: string;
|
||||
strictSSL?: boolean;
|
||||
}
|
||||
|
||||
export function getProxyAgent(rawRequestURL: string, options: IOptions = {}): any {
|
||||
const requestURL = parseUrl(rawRequestURL);
|
||||
const proxyURL = options.proxyUrl || getSystemProxyURI(requestURL);
|
||||
|
||||
if (!proxyURL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const proxyEndpoint = parseUrl(proxyURL);
|
||||
|
||||
if (!/^https?:$/.test(proxyEndpoint.protocol)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
host: proxyEndpoint.hostname,
|
||||
port: Number(proxyEndpoint.port),
|
||||
auth: proxyEndpoint.auth,
|
||||
rejectUnauthorized: (typeof options.strictSSL === 'boolean') ? options.strictSSL : true
|
||||
};
|
||||
|
||||
return requestURL.protocol === 'http:' ? new HttpProxyAgent(opts) : new HttpsProxyAgent(opts);
|
||||
}
|
||||
37
extensions/json/server/src/utils/strings.ts
Normal file
37
extensions/json/server/src/utils/strings.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export function startsWith(haystack: string, needle: string): boolean {
|
||||
if (haystack.length < needle.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < needle.length; i++) {
|
||||
if (haystack[i] !== needle[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if haystack ends with needle.
|
||||
*/
|
||||
export function endsWith(haystack: string, needle: string): boolean {
|
||||
let diff = haystack.length - needle.length;
|
||||
if (diff > 0) {
|
||||
return haystack.lastIndexOf(needle) === haystack.length - needle.length;
|
||||
} else if (diff === 0) {
|
||||
return haystack === needle;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function convertSimple2RegExpPattern(pattern: string): string {
|
||||
return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*');
|
||||
}
|
||||
334
extensions/json/server/src/utils/uri.ts
Normal file
334
extensions/json/server/src/utils/uri.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
||||
function fixedEncodeURIComponent(str: string): string {
|
||||
return encodeURIComponent(str).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
|
||||
* This class is a simple parser which creates the basic component paths
|
||||
* (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
|
||||
* and encoding.
|
||||
*
|
||||
* foo://example.com:8042/over/there?name=ferret#nose
|
||||
* \_/ \______________/\_________/ \_________/ \__/
|
||||
* | | | | |
|
||||
* scheme authority path query fragment
|
||||
* | _____________________|__
|
||||
* / \ / \
|
||||
* urn:example:animal:ferret:nose
|
||||
*
|
||||
*
|
||||
*/
|
||||
export default class URI {
|
||||
|
||||
private static _empty = '';
|
||||
private static _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
||||
private static _driveLetterPath = /^\/[a-zA-z]:/;
|
||||
private static _driveLetter = /^[a-zA-z]:/;
|
||||
|
||||
private _scheme: string;
|
||||
private _authority: string;
|
||||
private _path: string;
|
||||
private _query: string;
|
||||
private _fragment: string;
|
||||
|
||||
constructor() {
|
||||
this._scheme = URI._empty;
|
||||
this._authority = URI._empty;
|
||||
this._path = URI._empty;
|
||||
this._query = URI._empty;
|
||||
this._fragment = URI._empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
* The part before the first colon.
|
||||
*/
|
||||
get scheme() {
|
||||
return this._scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
get authority() {
|
||||
return this._authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
get path() {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
/**
|
||||
* query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
get query() {
|
||||
return this._query;
|
||||
}
|
||||
|
||||
/**
|
||||
* fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
get fragment() {
|
||||
return this._fragment;
|
||||
}
|
||||
|
||||
// ---- filesystem path -----------------------
|
||||
|
||||
private _fsPath: string;
|
||||
|
||||
/**
|
||||
* Returns a string representing the corresponding file system path of this URI.
|
||||
* Will handle UNC paths and normalize windows drive letters to lower-case. Also
|
||||
* 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.
|
||||
*/
|
||||
get fsPath() {
|
||||
if (!this._fsPath) {
|
||||
var value: string;
|
||||
if (this._authority && this.scheme === 'file') {
|
||||
// unc path: file://shares/c$/far/boo
|
||||
value = `//${this._authority}${this._path}`;
|
||||
} else if (URI._driveLetterPath.test(this._path)) {
|
||||
// windows drive letter: file:///c:/far/boo
|
||||
value = this._path[1].toLowerCase() + this._path.substr(2);
|
||||
} else {
|
||||
// other path
|
||||
value = this._path;
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
value = value.replace(/\//g, '\\');
|
||||
}
|
||||
this._fsPath = value;
|
||||
}
|
||||
return this._fsPath;
|
||||
}
|
||||
|
||||
// ---- modify to new -------------------------
|
||||
|
||||
public with(scheme: string, authority: string, path: string, query: string, fragment: string): URI {
|
||||
var ret = new URI();
|
||||
ret._scheme = scheme || this.scheme;
|
||||
ret._authority = authority || this.authority;
|
||||
ret._path = path || this.path;
|
||||
ret._query = query || this.query;
|
||||
ret._fragment = fragment || this.fragment;
|
||||
URI._validate(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public withScheme(value: string): URI {
|
||||
return this.with(value, undefined, undefined, undefined, undefined);
|
||||
}
|
||||
|
||||
public withAuthority(value: string): URI {
|
||||
return this.with(undefined, value, undefined, undefined, undefined);
|
||||
}
|
||||
|
||||
public withPath(value: string): URI {
|
||||
return this.with(undefined, undefined, value, undefined, undefined);
|
||||
}
|
||||
|
||||
public withQuery(value: string): URI {
|
||||
return this.with(undefined, undefined, undefined, value, undefined);
|
||||
}
|
||||
|
||||
public withFragment(value: string): URI {
|
||||
return this.with(undefined, undefined, undefined, undefined, value);
|
||||
}
|
||||
|
||||
// ---- parse & validate ------------------------
|
||||
|
||||
public static parse(value: string): URI {
|
||||
var ret = URI._parse(value);
|
||||
ret = ret.with(undefined,
|
||||
decodeURIComponent(ret.authority),
|
||||
decodeURIComponent(ret.path),
|
||||
decodeURIComponent(ret.query),
|
||||
decodeURIComponent(ret.fragment));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static file(path: string): URI {
|
||||
path = path.replace(/\\/g, '/');
|
||||
path = path.replace(/%/g, '%25');
|
||||
path = path.replace(/#/g, '%23');
|
||||
path = path.replace(/\?/g, '%3F');
|
||||
path = URI._driveLetter.test(path)
|
||||
? '/' + path
|
||||
: path;
|
||||
|
||||
var ret = URI._parse(path);
|
||||
if (ret.scheme || ret.fragment || ret.query) {
|
||||
throw new Error('Path contains a scheme, fragment or a query. Can not convert it to a file uri.');
|
||||
}
|
||||
|
||||
ret = ret.with('file', undefined,
|
||||
decodeURIComponent(ret.path),
|
||||
undefined, undefined);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static _parse(value: string): URI {
|
||||
var ret = new URI();
|
||||
var match = URI._regexp.exec(value);
|
||||
if (match) {
|
||||
ret._scheme = match[2] || ret._scheme;
|
||||
ret._authority = match[4] || ret._authority;
|
||||
ret._path = match[5] || ret._path;
|
||||
ret._query = match[7] || ret._query;
|
||||
ret._fragment = match[9] || ret._fragment;
|
||||
};
|
||||
URI._validate(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static create(scheme?: string, authority?: string, path?: string, query?: string, fragment?: string): URI {
|
||||
return new URI().with(scheme, authority, path, query, fragment);
|
||||
}
|
||||
|
||||
private static _validate(ret: URI): void {
|
||||
|
||||
// validation
|
||||
// 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.authority && ret.path && ret.path[0] !== '/') {
|
||||
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');
|
||||
}
|
||||
if (!ret.authority && ret.path.indexOf('//') === 0) {
|
||||
throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")');
|
||||
}
|
||||
}
|
||||
|
||||
// ---- printing/externalize ---------------------------
|
||||
|
||||
private _formatted: string;
|
||||
|
||||
public toString(): string {
|
||||
if (!this._formatted) {
|
||||
var parts: string[] = [];
|
||||
|
||||
if (this._scheme) {
|
||||
parts.push(this._scheme);
|
||||
parts.push(':');
|
||||
}
|
||||
if (this._authority || this._scheme === 'file') {
|
||||
parts.push('//');
|
||||
}
|
||||
if (this._authority) {
|
||||
var authority = this._authority,
|
||||
idx: number;
|
||||
|
||||
authority = authority.toLowerCase();
|
||||
idx = authority.indexOf(':');
|
||||
if (idx === -1) {
|
||||
parts.push(fixedEncodeURIComponent(authority));
|
||||
} else {
|
||||
parts.push(fixedEncodeURIComponent(authority.substr(0, idx)));
|
||||
parts.push(authority.substr(idx));
|
||||
}
|
||||
}
|
||||
if (this._path) {
|
||||
// encode every segment of the path
|
||||
var path = this._path,
|
||||
segments: string[];
|
||||
|
||||
// lower-case win drive letters in /C:/fff
|
||||
if (URI._driveLetterPath.test(path)) {
|
||||
path = '/' + path[1].toLowerCase() + path.substr(2);
|
||||
} else if (URI._driveLetter.test(path)) {
|
||||
path = path[0].toLowerCase() + path.substr(1);
|
||||
}
|
||||
segments = path.split('/');
|
||||
for (var i = 0, len = segments.length; i < len; i++) {
|
||||
segments[i] = fixedEncodeURIComponent(segments[i]);
|
||||
}
|
||||
parts.push(segments.join('/'));
|
||||
}
|
||||
if (this._query) {
|
||||
// in http(s) querys often use 'key=value'-pairs and
|
||||
// ampersand characters for multiple pairs
|
||||
var encoder = /https?/i.test(this.scheme)
|
||||
? encodeURI
|
||||
: fixedEncodeURIComponent;
|
||||
|
||||
parts.push('?');
|
||||
parts.push(encoder(this._query));
|
||||
}
|
||||
if (this._fragment) {
|
||||
parts.push('#');
|
||||
parts.push(fixedEncodeURIComponent(this._fragment));
|
||||
}
|
||||
this._formatted = parts.join('');
|
||||
}
|
||||
return this._formatted;
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
public static isURI(thing: any): thing is URI {
|
||||
if (thing instanceof URI) {
|
||||
return true;
|
||||
}
|
||||
if(!thing) {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).scheme !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).authority !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).fsPath !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).query !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).fragment !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).with !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).withScheme !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).withAuthority !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).withPath !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).withQuery !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).withFragment !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).toString !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).toJSON !== 'function') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user