Ayaz Hafiz a44479d0a0 style(common): remove unnecessary private annotation (#34734)
The `@private` annotation on a function upsets tsc, and appears
unnecessary for non-exported functions.

PR Close #34734
2020-01-13 07:17:42 -08:00

337 lines
10 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* A codec for encoding and decoding URL parts.
*
* @publicApi
**/
export abstract class UrlCodec {
/**
* Encodes the path from the provided string
*
* @param path The path string
*/
abstract encodePath(path: string): string;
/**
* Decodes the path from the provided string
*
* @param path The path string
*/
abstract decodePath(path: string): string;
/**
* Encodes the search string from the provided string or object
*
* @param path The path string or object
*/
abstract encodeSearch(search: string|{[k: string]: unknown}): string;
/**
* Decodes the search objects from the provided string
*
* @param path The path string
*/
abstract decodeSearch(search: string): {[k: string]: unknown};
/**
* Encodes the hash from the provided string
*
* @param path The hash string
*/
abstract encodeHash(hash: string): string;
/**
* Decodes the hash from the provided string
*
* @param path The hash string
*/
abstract decodeHash(hash: string): string;
/**
* Normalizes the URL from the provided string
*
* @param path The URL string
*/
abstract normalize(href: string): string;
/**
* Normalizes the URL from the provided string, search, hash, and base URL parameters
*
* @param path The URL path
* @param search The search object
* @param hash The has string
* @param baseUrl The base URL for the URL
*/
abstract normalize(path: string, search: {[k: string]: unknown}, hash: string, baseUrl?: string):
string;
/**
* Checks whether the two strings are equal
* @param valA First string for comparison
* @param valB Second string for comparison
*/
abstract areEqual(valA: string, valB: string): boolean;
/**
* Parses the URL string based on the base URL
*
* @param url The full URL string
* @param base The base for the URL
*/
abstract parse(url: string, base?: string): {
href: string,
protocol: string,
host: string,
search: string,
hash: string,
hostname: string,
port: string,
pathname: string
};
}
/**
* A `UrlCodec` that uses logic from AngularJS to serialize and parse URLs
* and URL parameters.
*
* @publicApi
*/
export class AngularJSUrlCodec implements UrlCodec {
// https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L15
encodePath(path: string): string {
const segments = path.split('/');
let i = segments.length;
while (i--) {
// decode forward slashes to prevent them from being double encoded
segments[i] = encodeUriSegment(segments[i].replace(/%2F/g, '/'));
}
path = segments.join('/');
return _stripIndexHtml((path && path[0] !== '/' && '/' || '') + path);
}
// https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L42
encodeSearch(search: string|{[k: string]: unknown}): string {
if (typeof search === 'string') {
search = parseKeyValue(search);
}
search = toKeyValue(search);
return search ? '?' + search : '';
}
// https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L44
encodeHash(hash: string) {
hash = encodeUriSegment(hash);
return hash ? '#' + hash : '';
}
// https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L27
decodePath(path: string, html5Mode = true): string {
const segments = path.split('/');
let i = segments.length;
while (i--) {
segments[i] = decodeURIComponent(segments[i]);
if (html5Mode) {
// encode forward slashes to prevent them from being mistaken for path separators
segments[i] = segments[i].replace(/\//g, '%2F');
}
}
return segments.join('/');
}
// https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L72
decodeSearch(search: string) { return parseKeyValue(search); }
// https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L73
decodeHash(hash: string) {
hash = decodeURIComponent(hash);
return hash[0] === '#' ? hash.substring(1) : hash;
}
// https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L149
// https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L42
normalize(href: string): string;
normalize(path: string, search: {[k: string]: unknown}, hash: string, baseUrl?: string): string;
normalize(pathOrHref: string, search?: {[k: string]: unknown}, hash?: string, baseUrl?: string):
string {
if (arguments.length === 1) {
const parsed = this.parse(pathOrHref, baseUrl);
if (typeof parsed === 'string') {
return parsed;
}
const serverUrl =
`${parsed.protocol}://${parsed.hostname}${parsed.port ? ':' + parsed.port : ''}`;
return this.normalize(
this.decodePath(parsed.pathname), this.decodeSearch(parsed.search),
this.decodeHash(parsed.hash), serverUrl);
} else {
const encPath = this.encodePath(pathOrHref);
const encSearch = search && this.encodeSearch(search) || '';
const encHash = hash && this.encodeHash(hash) || '';
let joinedPath = (baseUrl || '') + encPath;
if (!joinedPath.length || joinedPath[0] !== '/') {
joinedPath = '/' + joinedPath;
}
return joinedPath + encSearch + encHash;
}
}
areEqual(valA: string, valB: string) { return this.normalize(valA) === this.normalize(valB); }
// https://github.com/angular/angular.js/blob/864c7f0/src/ng/urlUtils.js#L60
parse(url: string, base?: string) {
try {
// Safari 12 throws an error when the URL constructor is called with an undefined base.
const parsed = !base ? new URL(url) : new URL(url, base);
return {
href: parsed.href,
protocol: parsed.protocol ? parsed.protocol.replace(/:$/, '') : '',
host: parsed.host,
search: parsed.search ? parsed.search.replace(/^\?/, '') : '',
hash: parsed.hash ? parsed.hash.replace(/^#/, '') : '',
hostname: parsed.hostname,
port: parsed.port,
pathname: (parsed.pathname.charAt(0) === '/') ? parsed.pathname : '/' + parsed.pathname
};
} catch (e) {
throw new Error(`Invalid URL (${url}) with base (${base})`);
}
}
}
function _stripIndexHtml(url: string): string {
return url.replace(/\/index.html$/, '');
}
/**
* Tries to decode the URI component without throwing an exception.
*
* @param str value potential URI component to check.
* @returns the decoded URI if it can be decoded or else `undefined`.
*/
function tryDecodeURIComponent(value: string): string|undefined {
try {
return decodeURIComponent(value);
} catch (e) {
// Ignore any invalid uri component.
return undefined;
}
}
/**
* Parses an escaped url query string into key-value pairs. Logic taken from
* https://github.com/angular/angular.js/blob/864c7f0/src/Angular.js#L1382
*/
function parseKeyValue(keyValue: string): {[k: string]: unknown} {
const obj: {[k: string]: unknown} = {};
(keyValue || '').split('&').forEach((keyValue) => {
let splitPoint, key, val;
if (keyValue) {
key = keyValue = keyValue.replace(/\+/g, '%20');
splitPoint = keyValue.indexOf('=');
if (splitPoint !== -1) {
key = keyValue.substring(0, splitPoint);
val = keyValue.substring(splitPoint + 1);
}
key = tryDecodeURIComponent(key);
if (typeof key !== 'undefined') {
val = typeof val !== 'undefined' ? tryDecodeURIComponent(val) : true;
if (!obj.hasOwnProperty(key)) {
obj[key] = val;
} else if (Array.isArray(obj[key])) {
(obj[key] as unknown[]).push(val);
} else {
obj[key] = [obj[key], val];
}
}
}
});
return obj;
}
/**
* Serializes into key-value pairs. Logic taken from
* https://github.com/angular/angular.js/blob/864c7f0/src/Angular.js#L1409
*/
function toKeyValue(obj: {[k: string]: unknown}) {
const parts: unknown[] = [];
for (const key in obj) {
let value = obj[key];
if (Array.isArray(value)) {
value.forEach((arrayValue) => {
parts.push(
encodeUriQuery(key, true) +
(arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
});
} else {
parts.push(
encodeUriQuery(key, true) +
(value === true ? '' : '=' + encodeUriQuery(value as any, true)));
}
}
return parts.length ? parts.join('&') : '';
}
/**
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
* segments:
* segment = *pchar
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* pct-encoded = "%" HEXDIG HEXDIG
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*
* Logic from https://github.com/angular/angular.js/blob/864c7f0/src/Angular.js#L1437
*/
function encodeUriSegment(val: string) {
return encodeUriQuery(val, true)
.replace(/%26/gi, '&')
.replace(/%3D/gi, '=')
.replace(/%2B/gi, '+');
}
/**
* This method is intended for encoding *key* or *value* parts of query component. We need a custom
* method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
* encoded per http://tools.ietf.org/html/rfc3986:
* query = *( pchar / "/" / "?" )
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* pct-encoded = "%" HEXDIG HEXDIG
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*
* Logic from https://github.com/angular/angular.js/blob/864c7f0/src/Angular.js#L1456
*/
function encodeUriQuery(val: string, pctEncodeSpaces: boolean = false) {
return encodeURIComponent(val)
.replace(/%40/gi, '@')
.replace(/%3A/gi, ':')
.replace(/%24/g, '$')
.replace(/%2C/gi, ',')
.replace(/%3B/gi, ';')
.replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}