Remove network.URL

This commit is contained in:
Alex Dima
2016-02-20 14:43:38 +01:00
parent 676b8b8881
commit ee05e79cb5
4 changed files with 24 additions and 569 deletions
+1 -420
View File
@@ -4,425 +4,6 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import assert = require('vs/base/common/assert');
import objects = require('vs/base/common/objects');
import strings = require('vs/base/common/strings');
import hash = require('vs/base/common/hash');
import paths = require('vs/base/common/paths');
import URI from 'vs/base/common/uri';
var _colon = ':'.charCodeAt(0),
_slash = '/'.charCodeAt(0),
_questionMark = '?'.charCodeAt(0),
_hash = '#'.charCodeAt(0);
export class ParsedUrl {
private spec:string;
private specLength:number;
private schemeStart:number;
private domainStart:number;
private portStart:number;
private pathStart:number;
private queryStringStart:number;
private fragmentIdStart:number;
constructor(spec:string) {
this.spec = spec || strings.empty;
this.specLength = this.spec.length;
this.parse();
}
private forwardSubstring(startIndex:number, endIndex:number): string {
if (startIndex < endIndex) {
return this.spec.substring(startIndex, endIndex);
}
return strings.empty;
}
/**
* http for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getScheme(): string {
return this.forwardSubstring(this.schemeStart, this.domainStart - 1);
}
/**
* http: for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getProtocol(): string {
return this.forwardSubstring(this.schemeStart, this.domainStart);
}
/**
* www.test.com for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getDomain(): string {
return this.forwardSubstring(this.domainStart + 2, this.portStart);
}
/**
* 8000 for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getPort(): string {
return this.forwardSubstring(this.portStart + 1, this.pathStart);
}
/**
* www.test.com:8000 for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getHost(): string {
return this.forwardSubstring(this.domainStart + 2, this.pathStart);
}
/**
* /this/that/theother.html for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getPath(): string {
return this.forwardSubstring(this.pathStart, this.queryStringStart);
}
/**
* query=foo for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getQueryString(): string {
return this.forwardSubstring(this.queryStringStart + 1, this.fragmentIdStart);
}
/**
* hash for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getFragmentId(): string {
return this.forwardSubstring(this.fragmentIdStart + 1, this.specLength);
}
/**
* http://www.test.com:8000 for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getAllBeforePath(): string {
return this.forwardSubstring(0, this.pathStart);
}
/**
* http://www.test.com:8000/this/that/theother.html?query=foo for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getAllBeforeFragmentId(): string {
return this.forwardSubstring(0, this.fragmentIdStart);
}
/**
* Combine with a relative url, returns absolute url
* e.g.
* http://www.test.com/this/that/theother.html?query=foo#hash=hash
* combined with ../test.js?query=foo#hash
* results in http://www.test.com/this/test.js?query=foo#hash
*/
public combine(relativeUrl:string):string {
var questionMarkIndex = relativeUrl.indexOf('?');
var hashIndex = relativeUrl.indexOf('#');
var suffixIndex = relativeUrl.length;
if (questionMarkIndex !== -1 && questionMarkIndex < suffixIndex) {
suffixIndex = questionMarkIndex;
}
if (hashIndex !== -1 && hashIndex < suffixIndex) {
suffixIndex = hashIndex;
}
var relativeUrlPath = relativeUrl.substring(0, suffixIndex);
var relativeUrlSuffix = relativeUrl.substring(suffixIndex);
relativeUrlPath = relativeUrlPath.replace('\\', '/');
var resultPath: string;
if (strings.startsWith(relativeUrlPath, '/')) {
// Looks like an absolute URL
resultPath = paths.join(relativeUrlPath);
} else {
resultPath = paths.join(paths.dirname(this.getPath()), relativeUrlPath);
}
while (resultPath.charAt(0) === '/') {
resultPath = resultPath.substr(1);
}
while (resultPath.indexOf('../') === 0) {
resultPath = resultPath.substr(3);
}
return this.getAllBeforePath() + '/' + resultPath + relativeUrlSuffix;
}
// scheme://domain:port/path?query_string#fragment_id
private parse(): void {
var IN_SCHEME = 0,
IN_DOMAIN = 1,
IN_PORT = 2,
IN_PATH = 3,
IN_QUERY_STRING = 4,
state = IN_SCHEME,
spec = this.spec,
length = this.specLength,
i:number,
prevChCode:number = -1,
prevPrevChCode:number = -1,
chCode:number;
this.schemeStart = 0;
this.domainStart = this.specLength;
this.portStart = this.specLength;
this.pathStart = this.specLength;
this.queryStringStart = this.specLength;
this.fragmentIdStart = this.specLength;
for (i = 0; i < length; i++) {
chCode = spec.charCodeAt(i);
switch (state) {
case IN_SCHEME:
if (prevChCode === _slash && chCode === _slash) {
// going into the domain
state = IN_DOMAIN;
this.domainStart = i - 1;
}
break;
case IN_DOMAIN:
if (chCode === _colon) {
// going into the port
state = IN_PORT;
this.portStart = i;
} else if (chCode === _slash) {
// skipping the port, going straight to the path
state = IN_PATH;
this.portStart = i;
this.pathStart = i;
} else if (chCode === _hash) {
// skipping the port, path & query string, going straight to the fragment, we can halt now
this.portStart = i;
this.pathStart = i;
this.queryStringStart = i;
this.fragmentIdStart = i;
i = length;
}
break;
case IN_PORT:
if (chCode === _slash) {
// going into the path
state = IN_PATH;
this.pathStart = i;
} else if (chCode === _hash) {
// skipping the path & query string, going straight to the fragment, we can halt now
this.pathStart = i;
this.queryStringStart = i;
this.fragmentIdStart = i;
i = length;
}
break;
case IN_PATH:
if (chCode === _questionMark) {
// going in to the query string
state = IN_QUERY_STRING;
this.queryStringStart = i;
} else if (chCode === _hash) {
// skipping the query string, going straight to the fragment, we can halt now
this.queryStringStart = i;
this.fragmentIdStart = i;
i = length;
}
break;
case IN_QUERY_STRING:
if (chCode === _hash) {
// going into the hash, we can halt now
this.fragmentIdStart = i;
i = length;
}
break;
}
prevPrevChCode = prevChCode;
prevChCode = chCode;
}
if (state === IN_SCHEME) {
// Looks like we had a very bad url
this.schemeStart = this.specLength;
}
}
}
export class URL extends URI implements objects.IEqualable {
/**
* Creates a new URL from the provided value
* by decoding it first.
* @param value An encoded url value.
*/
public static fromEncoded(value:string):URL {
return new URL(decodeURIComponent(value));
}
public static fromValue(value:string):URL {
return new URL(value);
}
public static fromUri(value: URI): URL {
if (!value) {
return <any>value;
} else if (value instanceof URL) {
return value;
} else {
return new URL(value);
}
}
private _spec:string;
private _uri: URI;
private _parsed:ParsedUrl;
constructor(spec: string);
constructor(spec: URI);
constructor(stringOrURI: string|URI) {
super();
assert.ok(!!stringOrURI, 'spec must not be null');
if(typeof stringOrURI === 'string') {
this._uri = URI.parse(stringOrURI);
} else {
this._uri = stringOrURI;
}
this._spec = this._uri.toString(); // make sure spec is normalized
this._parsed = null;
}
public equals(other:any):boolean {
if (this.toString() !== String(other)) {
return false;
}
return (other instanceof URL || other instanceof URI);
}
public hashCode():number {
return hash.computeMurmur2StringHashCode(this._spec);
}
public isInMemory(): boolean {
return this.scheme === schemas.inMemory;
}
/**
* http for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getScheme():string {
this._ensureParsedUrl();
return this._parsed.getScheme();
}
/**
* /this/that/theother.html for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public getPath():string {
this._ensureParsedUrl();
return this._parsed.getPath();
}
/**
* Strips out the hash part of the URL.
* http://www.test.com:8000/this/that/theother.html?query=foo for http://www.test.com:8000/this/that/theother.html?query=foo#hash
*/
public toUnique():string {
this._ensureParsedUrl();
return this._parsed.getAllBeforeFragmentId();
}
public startsWith(other:URL):boolean {
return strings.startsWith(this._spec, other._spec);
}
/**
* Combine with a relative url, returns absolute url
* e.g.
* http://www.test.com/this/that/theother.html?query=foo#hash=hash
* combined with ../test.js?query=foo#hash
* results in http://www.test.com/this/test.js?query=foo#hash
*/
public combine(relativeUrl:string):URL {
this._ensureParsedUrl();
return new URL(this._parsed.combine(relativeUrl));
}
private _ensureParsedUrl(): void {
if(this._parsed === null) {
this._parsed = new ParsedUrl(this._spec);
}
}
// ----- URI implementation -------------------------
public get scheme(): string {
return this._uri.scheme;
}
public get authority(): string {
return this._uri.authority;
}
public get path(): string {
return this._uri.path;
}
public get fsPath(): string {
return this._uri.fsPath;
}
public get query(): string {
return this._uri.query;
}
public get fragment(): string {
return this._uri.fragment;
}
public withScheme(value: string): URI {
return URI.create(value, this.authority, this.fsPath, this.query, this.fragment);
}
public withAuthority(value: string): URI {
return URI.create(this.scheme, value, this.fsPath, this.query, this.fragment);
}
public withPath(value: string): URI {
return URI.create(this.scheme, this.authority, value, this.query, this.fragment);
}
public withQuery(value: string): URI {
return URI.create(this.scheme, this.authority, this.fsPath, value, this.fragment);
}
public withFragment(value: string): URI {
return URI.create(this.scheme, this.authority, this.fsPath, this.query, value);
}
public with(scheme: string, authority: string, path: string, query: string, fragment: string): URI {
return URI.create(scheme, authority, path, query, fragment);
}
public toString():string {
return this._spec;
}
public toJSON(): any {
return this.toString();
}
}
export namespace schemas {
/**
@@ -436,4 +17,4 @@ export namespace schemas {
export var https:string = 'https';
export var file:string = 'file';
}
}
-128
View File
@@ -6,21 +6,8 @@
import * as assert from 'assert';
import URI from 'vs/base/common/uri';
import { URL, ParsedUrl } from 'vs/base/common/network';
function assertUrl(raw:string, scheme:string, domain:string, port:string, path:string, queryString:string, fragmentId:string): void {
var url = new ParsedUrl(raw);
assert.equal(url.getScheme(), scheme, 'getScheme ok for ' + raw);
var protocol = scheme ? scheme + ':' : scheme;
assert.equal(url.getProtocol(), protocol, 'getProtocol ok for ' + raw);
assert.equal(url.getDomain(), domain, 'getDomain ok for ' + raw);
assert.equal(url.getPort(), port, 'getPort ok for ' + raw);
var host = domain + (port ? ':' + port : '');
assert.equal(url.getHost(), host, 'getHost ok for ' + raw);
assert.equal(url.getPath(), path, 'getPath ok for ' + raw);
assert.equal(url.getQueryString(), queryString, 'getQueryString ok for ' + raw);
assert.equal(url.getFragmentId(), fragmentId, 'getFragmentId ok for ' + raw);
// check for equivalent behaviour
var uri = URI.parse(raw);
assert.equal(uri.scheme, scheme);
@@ -30,12 +17,6 @@ function assertUrl(raw:string, scheme:string, domain:string, port:string, path:s
assert.equal(uri.fragment, fragmentId);
}
function assertCombine(base:string, relativeUrl:string, expectedUrl:string): void {
var url = new ParsedUrl(base);
var result = url.combine(relativeUrl);
assert.equal(result, expectedUrl, 'combine ok for ' + base + ' and ' + relativeUrl);
}
suite('Network', () => {
test('urls', () => {
assertUrl('http://www.test.com:8000/this/that/theother.html?query=foo#hash',
@@ -107,114 +88,5 @@ suite('Network', () => {
);
assertUrl('file:///c/far/boo/file.cs', 'file', '', '', '/c/far/boo/file.cs', '', '');
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '\\test.js',
'http://www.test.com:8000/test.js'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '/test.js',
'http://www.test.com:8000/test.js'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '////test.js',
'http://www.test.com:8000/test.js'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '\\test.js?token=123',
'http://www.test.com:8000/test.js?token=123'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '\\test.js?query=foo#hash',
'http://www.test.com:8000/test.js?query=foo#hash'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '\\test.js#hash',
'http://www.test.com:8000/test.js#hash'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', 'test.js',
'http://www.test.com:8000/this/that/test.js'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '../test.js',
'http://www.test.com:8000/this/test.js'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '..\\test.js',
'http://www.test.com:8000/this/test.js'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '..\\test.js?token=123',
'http://www.test.com:8000/this/test.js?token=123'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '..\\test.js?query=foo#hash',
'http://www.test.com:8000/this/test.js?query=foo#hash'
);
assertCombine(
'http://www.test.com:8000/this/that/theother.html?query=foo#hash', '..\\test.js#hash',
'http://www.test.com:8000/this/test.js#hash'
);
assertCombine(
'http://www.test.com:8000/this/that/', 'test.js',
'http://www.test.com:8000/this/that/test.js'
);
assertCombine(
'http://www.test.com:8000/this/that/', './test.js',
'http://www.test.com:8000/this/that/test.js'
);
assertCombine(
'http://www.test.com:8000/', './test.js',
'http://www.test.com:8000/test.js'
);
assertCombine(
'http://www.test.com:8000/', './././test.js',
'http://www.test.com:8000/test.js'
);
assertCombine(
'http://www.test.com:8000', './././test.js',
'http://www.test.com:8000/test.js'
);
assertCombine(
'http://www.test.com:8000', 'test.js',
'http://www.test.com:8000/test.js'
);
assertCombine(
'http://www.test.com', '../test.js',
'http://www.test.com/test.js'
);
assertCombine(
'http://www.test.com', 'a/b/../../../test.js',
'http://www.test.com/test.js'
);
assertCombine(
'http://www.test.com/a/b', 'a/b/../../../../../test.js',
'http://www.test.com/test.js'
);
assertCombine(
'http://www.test.com', 'test.js',
'http://www.test.com/test.js'
);
assertCombine(
'http://www.test.com', 'a/b/test.js',
'http://www.test.com/a/b/test.js'
);
assertCombine(
'http://www.test.com', './a/b/test.js',
'http://www.test.com/a/b/test.js'
);
assertCombine(
'http://www.test.com/index.html', './a/b/test.js',
'http://www.test.com/a/b/test.js'
);
var url = new URL('inmemory://model/1#css');
assert.equal(url.toUnique(), 'inmemory://model/1');
});
});
+18 -18
View File
@@ -20,6 +20,7 @@ import {getScanner, IHTMLScanner} from 'vs/languages/html/common/htmlScanner';
import {isTag, DELIM_END, DELIM_START, DELIM_ASSIGN, ATTRIB_NAME, ATTRIB_VALUE} from 'vs/languages/html/common/htmlTokenTypes';
import {isEmptyElement} from 'vs/languages/html/common/htmlEmptyTagsShared';
import {filterSuggestions} from 'vs/editor/common/modes/supports/suggestSupport';
import paths = require('vs/base/common/paths');
enum LinkDetectionState {
LOOKING_FOR_HREF_OR_SRC = 1,
@@ -516,11 +517,8 @@ export class HTMLWorker {
}
public static _getWorkspaceUrl(modelAbsoluteUri: URI, rootAbsoluteUri: URI, tokenContent: string): string {
var modelAbsoluteUrl = network.URL.fromUri(modelAbsoluteUri);
var rootAbsoluteUrl = network.URL.fromUri(rootAbsoluteUri);
tokenContent = HTMLWorker._stripQuotes(tokenContent);
if (/^\s*javascript\:/i.test(tokenContent) || /^\s*\#/i.test(tokenContent)) {
return null;
}
@@ -532,27 +530,29 @@ export class HTMLWorker {
if (/^\s*\/\//i.test(tokenContent)) {
// Absolute link (that does not name the protocol)
var modelScheme = modelAbsoluteUrl.getScheme();
var pickedScheme = 'http';
if (modelScheme === network.schemas.https) {
let pickedScheme = network.schemas.http;
if (modelAbsoluteUri.scheme === network.schemas.https) {
pickedScheme = network.schemas.https;
}
return pickedScheme + ':' + tokenContent.replace(/^\s*/g, '');
}
try {
var potentialResult = modelAbsoluteUrl.combine(tokenContent).toString();
} catch (err) {
// invalid URL
return null;
let modelPath = paths.dirname(modelAbsoluteUri.path);
let alternativeResultPath: string = null;
if (tokenContent.length > 0 && tokenContent.charAt(0) === '/') {
alternativeResultPath = tokenContent;
} else {
alternativeResultPath = paths.join(modelPath, tokenContent);
alternativeResultPath = alternativeResultPath.replace(/^(\/\.\.)+/, '');
}
let potentialResult = modelAbsoluteUri.withPath(alternativeResultPath).toString();
if (rootAbsoluteUrl && modelAbsoluteUrl.startsWith(rootAbsoluteUrl)) {
let rootAbsoluteUrlStr = (rootAbsoluteUri ? rootAbsoluteUri.toString() : null);
if (rootAbsoluteUrlStr && strings.startsWith(modelAbsoluteUri.toString(), rootAbsoluteUrlStr)) {
// The `rootAbsoluteUrl` is set and matches our current model
// We need to ensure that this `potentialResult` does not escape `rootAbsoluteUrl`
var rootAbsoluteUrlStr = rootAbsoluteUrl.toString();
var commonPrefixLength = strings.commonPrefixLength(rootAbsoluteUrlStr, potentialResult);
let commonPrefixLength = strings.commonPrefixLength(rootAbsoluteUrlStr, potentialResult);
if (strings.endsWith(rootAbsoluteUrlStr, '/')) {
commonPrefixLength = potentialResult.lastIndexOf('/', commonPrefixLength) + 1;
}
@@ -562,7 +562,7 @@ export class HTMLWorker {
return potentialResult;
}
private createLink(modelAbsoluteUrl: URI, rootAbsoluteUrl: network.URL, tokenContent: string, lineNumber: number, startColumn: number, endColumn: number): Modes.ILink {
private createLink(modelAbsoluteUrl: URI, rootAbsoluteUrl: URI, tokenContent: string, lineNumber: number, startColumn: number, endColumn: number): Modes.ILink {
var workspaceUrl = HTMLWorker._getWorkspaceUrl(modelAbsoluteUrl, rootAbsoluteUrl, tokenContent);
if (!workspaceUrl) {
return null;
@@ -596,15 +596,15 @@ export class HTMLWorker {
tokenContent: string,
link: Modes.ILink;
let rootAbsoluteUrl: network.URL = null;
let rootAbsoluteUrl: URI = null;
let workspace = this._contextService.getWorkspace();
if (workspace) {
// The workspace can be null in the no folder opened case
let strRootAbsoluteUrl = String(workspace.resource);
if (strRootAbsoluteUrl.charAt(strRootAbsoluteUrl.length - 1) === '/') {
rootAbsoluteUrl = new network.URL(strRootAbsoluteUrl);
rootAbsoluteUrl = URI.parse(strRootAbsoluteUrl);
} else {
rootAbsoluteUrl = new network.URL(strRootAbsoluteUrl + '/');
rootAbsoluteUrl = URI.parse(strRootAbsoluteUrl + '/');
}
}
@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import assert = require('assert')
import 'vs/languages/html/common/html.contribution';
import assert = require('assert');
import mm = require('vs/editor/common/model/mirrorModel');
import htmlWorker = require('vs/languages/html/common/htmlWorker');
import URI from 'vs/base/common/uri';
@@ -61,7 +62,7 @@ suite('HTML - worker', () => {
var proposalsFound = completion.suggestions.filter(function(suggestion: Modes.ISuggestion) {
return suggestion.label === label && (!type || suggestion.type === type) && (!codeSnippet || suggestion.codeSnippet === codeSnippet);
});
if (proposalsFound.length != 1) {
if (proposalsFound.length !== 1) {
assert.fail('Suggestion not found: ' + label + ', has ' + completion.suggestions.map(s => s.label).join(', '));
}
};
@@ -364,6 +365,7 @@ suite('HTML - worker', () => {
testLinkCreation('file:///C:/Alex/src/path/to/file.txt', null, 'file:///C:\\Alex\\src\\path\\to\\file.txt', 'file:///C:\\Alex\\src\\path\\to\\file.txt');
testLinkCreation('file:///C:/Alex/src/path/to/file.txt', 'file:///C:/Alex/src/', 'http://www.microsoft.com/', 'http://www.microsoft.com/');
testLinkCreation('file:///C:/Alex/src/path/to/file.txt', 'file:///C:/Alex/src/', 'https://www.microsoft.com/', 'https://www.microsoft.com/');
testLinkCreation('file:///C:/Alex/src/path/to/file.txt', 'file:///C:/Alex/src/', 'https://www.microsoft.com/?q=1#h', 'https://www.microsoft.com/?q=1#h');
testLinkCreation('file:///C:/Alex/src/path/to/file.txt', 'file:///C:/Alex/src/', ' //www.microsoft.com/', 'http://www.microsoft.com/');
testLinkCreation('file:///C:/Alex/src/path/to/file.txt', 'file:///C:/Alex/src/', 'a.js', 'file:///C:/Alex/src/path/to/a.js');
testLinkCreation('file:///C:/Alex/src/path/to/file.txt', 'file:///C:/Alex/src/', '/a.js', 'file:///C:/Alex/src/a.js');
@@ -373,7 +375,7 @@ suite('HTML - worker', () => {
testLinkCreation('https://www.test.com/path/to/file.txt', 'https://www.test.com', '//www.microsoft.com/', 'https://www.microsoft.com/');
// invalid uris don't throw
testLinkCreation('https://www.test.com/path/to/file.txt', 'https://www.test.com', '%', null);
testLinkCreation('https://www.test.com/path/to/file.txt', 'https://www.test.com', '%', 'https://www.test.com/path/to/%25');
// Bug #18314: Ctrl + Click does not open existing file if folder's name starts with 'c' character
testLinkCreation('file:///c:/Alex/working_dir/18314-link-detection/test.html', 'file:///c:/Alex/working_dir/18314-link-detection/', '/class/class.js', 'file:///c:/Alex/working_dir/18314-link-detection/class/class.js');