uri.ts 10.1 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  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 platform = require('vs/base/common/platform');

9 10 11 12 13

function _encode(ch: string): string {
	return '%' + ch.charCodeAt(0).toString(16).toUpperCase();
}

E
Erich Gamma 已提交
14
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
15 16
function encodeURIComponent2(str: string): string {
	return encodeURIComponent(str).replace(/[!'()*]/g, _encode);
E
Erich Gamma 已提交
17 18
}

19 20 21 22 23
function encodeNoop(str: string): string {
	return str;
}


E
Erich Gamma 已提交
24 25 26 27 28 29
/**
 * 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.
 *
30
 *       foo://example.com:8042/over/there?name=ferret#nose
E
Erich Gamma 已提交
31 32 33 34 35 36 37 38 39 40 41 42
 *       \_/   \______________/\_________/ \_________/ \__/
 *        |           |            |            |        |
 *     scheme     authority       path        query   fragment
 *        |   _____________________|__
 *       / \ /                        \
 *       urn:example:animal:ferret:nose
 *
 *
 */
export default class URI {

	private static _empty = '';
43
	private static _slash = '/';
E
Erich Gamma 已提交
44 45
	private static _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
	private static _driveLetterPath = /^\/[a-zA-z]:/;
J
Johannes Rieken 已提交
46
	private static _upperCaseDrive = /^(\/)?([A-Z]:)/;
47

E
Erich Gamma 已提交
48 49 50 51 52
	private _scheme: string;
	private _authority: string;
	private _path: string;
	private _query: string;
	private _fragment: string;
J
Johannes Rieken 已提交
53 54
	private _formatted: string;
	private _fsPath: string;
E
Erich Gamma 已提交
55 56 57 58 59 60 61

	constructor() {
		this._scheme = URI._empty;
		this._authority = URI._empty;
		this._path = URI._empty;
		this._query = URI._empty;
		this._fragment = URI._empty;
J
Johannes Rieken 已提交
62 63 64

		this._formatted = null;
		this._fsPath = null;
E
Erich Gamma 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
	}

	/**
	 * 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 -----------------------

	/**
P
Pascal Borreli 已提交
107
	 * Returns a string representing the corresponding file system path of this URI.
E
Erich Gamma 已提交
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
	 * 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 (platform.isWindows) {
				value = value.replace(/\//g, '\\');
			}
			this._fsPath = value;
		}
		return this._fsPath;
	}

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

135
	public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161

		if (!change) {
			return this;
		}

		let scheme = change.scheme || this.scheme;
		let authority = change.authority || this.authority;
		let path = change.path || this.path;
		let query = change.query || this.query;
		let fragment = change.fragment || this.fragment;

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

			return this;
		}

		const ret = new URI();
		ret._scheme = scheme;
		ret._authority = authority;
		ret._path = path;
		ret._query = query;
		ret._fragment = fragment;
E
Erich Gamma 已提交
162 163 164 165 166 167 168
		URI._validate(ret);
		return ret;
	}

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

	public static parse(value: string): URI {
169 170 171 172 173 174 175 176
		const ret = new URI();
		const data = URI._parseComponents(value);
		ret._scheme = data.scheme;
		ret._authority = decodeURIComponent(data.authority);
		ret._path = decodeURIComponent(data.path);
		ret._query = decodeURIComponent(data.query);
		ret._fragment = decodeURIComponent(data.fragment);
		URI._validate(ret);
E
Erich Gamma 已提交
177 178 179 180 181
		return ret;
	}

	public static file(path: string): URI {

182 183
		const ret = new URI();
		ret._scheme = 'file';
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207

		// normalize to fwd-slashes
		path = path.replace(/\\/g, URI._slash);

		// check for authority as used in UNC shares
		// or use the path as given
		if (path[0] === URI._slash && path[0] === path[1]) {
			let idx = path.indexOf(URI._slash, 2);
			if (idx === -1) {
				ret._authority = path.substring(2);
			} else {
				ret._authority = path.substring(2, idx);
				ret._path = path.substring(idx);
			}
		} else {
			ret._path = path;
		}

		// Ensure that path starts with a slash
		// or that it is at least a slash
		if (ret._path[0] !== URI._slash) {
			ret._path = URI._slash + ret._path;
		}

208
		URI._validate(ret);
209

E
Erich Gamma 已提交
210 211 212
		return ret;
	}

213 214 215 216 217 218 219 220 221 222 223
	private static _parseComponents(value: string): UriComponents {

		const ret: UriComponents = {
			scheme: URI._empty,
			authority: URI._empty,
			path: URI._empty,
			query: URI._empty,
			fragment: URI._empty,
		};

		const match = URI._regexp.exec(value);
E
Erich Gamma 已提交
224
		if (match) {
225 226 227 228 229
			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;
230
		}
E
Erich Gamma 已提交
231 232 233 234
		return ret;
	}

	public static create(scheme?: string, authority?: string, path?: string, query?: string, fragment?: string): URI {
235
		return new URI().with({ scheme, authority, path, query, fragment });
E
Erich Gamma 已提交
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
	}

	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 ---------------------------

256
	/**
257
	 *
258
	 * @param skipEncoding Do not encode the result, default is `false`
259
	 */
260 261 262 263 264 265 266 267 268
	public toString(skipEncoding: boolean = false): string {
		if (!skipEncoding) {
			if (!this._formatted) {
				this._formatted = URI._asFormatted(this, false);
			}
			return this._formatted;
		} else {
			// we don't cache that
			return URI._asFormatted(this, true);
J
Johannes Rieken 已提交
269 270 271
		}
	}

272
	private static _asFormatted(uri: URI, skipEncoding: boolean): string {
273

274
		const encoder = !skipEncoding
275 276 277
			? encodeURIComponent2
			: encodeNoop;

J
Johannes Rieken 已提交
278
		const parts: string[] = [];
E
Erich Gamma 已提交
279

J
Johannes Rieken 已提交
280 281 282 283 284 285 286 287 288 289 290
		let {scheme, authority, path, query, fragment} = uri;
		if (scheme) {
			parts.push(scheme, ':');
		}
		if (authority || scheme === 'file') {
			parts.push('//');
		}
		if (authority) {
			authority = authority.toLowerCase();
			let idx = authority.indexOf(':');
			if (idx === -1) {
291
				parts.push(encoder(authority));
J
Johannes Rieken 已提交
292
			} else {
293
				parts.push(encoder(authority.substr(0, idx)), authority.substr(idx));
E
Erich Gamma 已提交
294
			}
J
Johannes Rieken 已提交
295 296 297 298 299 300
		}
		if (path) {
			// lower-case windown drive letters in /C:/fff
			const m = URI._upperCaseDrive.exec(path);
			if (m) {
				path = m[1] + m[2].toLowerCase() + path.substr(m[1].length + m[2].length);
E
Erich Gamma 已提交
301 302
			}

J
Johannes Rieken 已提交
303
			// encode every segement but not slashes
304 305 306
			// make sure that # and ? are always encoded
			// when occurring in paths - otherwise the result
			// cannot be parsed back again
J
Johannes Rieken 已提交
307 308 309
			let lastIdx = 0;
			while(true) {
				let idx = path.indexOf(URI._slash, lastIdx);
E
Erich Gamma 已提交
310
				if (idx === -1) {
311
					parts.push(encoder(path.substring(lastIdx)).replace(/[#?]/, _encode));
J
Johannes Rieken 已提交
312
					break;
E
Erich Gamma 已提交
313
				}
314
				parts.push(encoder(path.substring(lastIdx, idx)).replace(/[#?]/, _encode), URI._slash);
J
Johannes Rieken 已提交
315 316
				lastIdx = idx + 1;
			};
E
Erich Gamma 已提交
317
		}
J
Johannes Rieken 已提交
318 319 320 321 322 323 324 325
		if (query) {
			parts.push('?', encoder(query));
		}
		if (fragment) {
			parts.push('#', encoder(fragment));
		}

		return parts.join(URI._empty);
E
Erich Gamma 已提交
326 327 328
	}

	public toJSON(): any {
329
		return <UriState> {
330 331 332 333 334 335
			scheme: this.scheme,
			authority: this.authority,
			path: this.path,
			fsPath: this.fsPath,
			query: this.query,
			fragment: this.fragment.replace(/URL_MARSHAL_REMOVE.*$/, ''), // TODO@Alex: implement derived resources (embedded mirror models) better
336 337
			external: this.toString().replace(/#?URL_MARSHAL_REMOVE.*$/, ''), // TODO@Alex: implement derived resources (embedded mirror models) better
			$mid: 1
E
Erich Gamma 已提交
338 339 340
		};
	}

341
	static revive(data: any): URI {
E
Erich Gamma 已提交
342
		let result = new URI();
343 344 345 346 347 348 349
		result._scheme = (<UriState> data).scheme;
		result._authority = (<UriState> data).authority;
		result._path = (<UriState> data).path;
		result._query = (<UriState> data).query;
		result._fragment = (<UriState> data).fragment;
		result._fsPath = (<UriState> data).fsPath;
		result._formatted = (<UriState>data).external;
350
		URI._validate(result);
E
Erich Gamma 已提交
351 352 353 354
		return result;
	}
}

355
interface UriComponents {
356 357 358 359 360
	scheme: string;
	authority: string;
	path: string;
	query: string;
	fragment: string;
361 362 363 364 365
}

interface UriState extends UriComponents {
	$mid: number;
	fsPath: string;
366
	external: string;
E
Erich Gamma 已提交
367
}