blob: baeca91bdd7570ef0779f318de1f4f1ccb0c315b [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
* @fileoverview Helpers for manipulating vanadium names.
* See vanadium/release/go/src/v.io/v23/naming/parse.go for the
* corresponding operations in golang.
* @private
*/
module.exports = {
clean: clean,
encodeAsNameElement: encodeAsNameElement,
decodeAsNameElement: decodeAsNameElement,
join: join,
isRooted: isRooted,
basename: basename,
stripBasename: stripBasename,
splitAddressName: splitAddressName,
blessingNamesFromAddress: blessingNamesFromAddress,
};
/**
* Normalizes a name by collapsing multiple slashes and removing any
* trailing slashes.
* @param {string} name The vanadium name.
* @returns {string} The clean name.
* @memberof module:vanadium.naming
*/
function clean(name) {
return _removeTailSlash(_squashMultipleSlashes(name));
}
/**
* Makes a string representable as a name element by escaping slashes.
* @param {string} nameElement The vanadium name element to be encoded.
* @returns {string} Encoded name element that does not contain slashes.
* @memberof module:vanadium.naming
*/
function encodeAsNameElement(nameElement) {
var output = nameElement.replace(/%/g, '%25').replace(/\//g, '%2F');
return output;
}
/**
* Decodes an encoded name element.
* Throws exception if encodedNameElement was not properly encoded.
* Note that this is more than the inverse of encodeAsNameElement since it can
* handle more hex encodings than / and %.
* This is intentional since we'll most likely want to add other letters to the
* set to be encoded.
* @param {string} encodedNameElement The encoded name element to be decoded.
* @returns {string} Decoded name element.
* @memberof module:vanadium.naming
*/
function decodeAsNameElement(encodedNameElement) {
// decodeURIComponent handles decoding hex percent encoded UTF-8 strings.
var output = decodeURIComponent(encodedNameElement);
return output;
}
/**
* <p>Joins parts of a name into a whole. The joined name will be cleaned; it
* only preserved the rootedness of the name components.</p>
* <p>Examples:</p>
* <pre>
* join(['a, b']) -> 'a/b'
* join('/a/b/', '//d') -> '/a/b/d'
* join('//a/b', 'c/') -> '/a/b/c'
* </pre>
* @param {...string} parts Either a single array that contains the strings
* to join or a variable number of string arguments that will be joined.
* @return {string} A joined string.
* @memberof module:vanadium.naming
*/
function join(parts) {
if (Array.isArray(parts)) {
while (parts.length > 0 && parts[0] === '') {
parts.splice(0, 1); // Remove empty strings; they add nothing to the join.
}
var joined = parts.join('/');
return clean(joined);
}
return join(Array.prototype.slice.call(arguments));
}
/**
* Determines if a name is rooted, that is beginning with a single '/'.
* @param {string} name The vanadium name.
* @return {boolean} True iff the name is rooted.
* @memberof module:vanadium.naming
*/
function isRooted(name) {
return name[0] === '/';
}
// TODO(nlacasse): Should we have a full fledged object parallel to
// naming.Endpoint in Go? Because this parsing is really really shabby!
/**
* blessingNamesFromAddress extracts the blessing names of the server with the
* provided address (endpoint).
*
* @param {string} address String representation of the server address (aka
* endpoint).
* @return {Array<string>} Blessing names extracted from address, or an empty
* list if none could be extracted.
* @memberof module:vanadium.naming
*/
function blessingNamesFromAddress(addr) {
var epversion = endpointVersion(addr);
if (isNaN(epversion)) {
// Not a well formed endpoint string.
// Might be in "host:port" format, if so extract blessing names from that.
// Format: [(<blessing name>)]@host:port
var open = addr.indexOf('(');
var close = addr.indexOf(')');
if (open === 0 && close > 0 && addr.indexOf('@') === (close + 1)) {
return addr.substr(1, close - 1).split(',');
}
return [];
}
var blessingNameField = 0;
switch(epversion) {
case 5:
blessingNameField = 5;
break;
case 6:
blessingNameField = 6;
break;
default:
throw new Error('endpoint version ' + epversion + ' not supported');
}
var start = 0;
// blessing names are the blessingNameField position.
for (var i = 0; i < blessingNameField; i++) {
start = addr.indexOf('@', start + 1);
}
return addr.substr(start + 1, addr.length - start - 3).split(',');
}
function endpointVersion(addr) {
// Poor approximation of a well-formed endpoint string.
// Format described in
// the Go library documentation for v.io/v23/naming.Endpoint. Must be at
// least 7 characters (shortest valid endpoint is: @1@@@@@)
if (addr.length < 7) {
return NaN;
}
// Must start with an '@' and end with an '@@'
if (addr.indexOf('@') !== 0) {
return NaN;
}
if (addr.lastIndexOf('@@') !== (addr.length - 2)) {
return NaN;
}
return parseWholeNumber(addr.split('@')[1]);
}
function parseWholeNumber(value) {
if (/^\d+$/.test(value)) {
return Number(value);
}
return NaN;
}
/**
* SplitAddressName takes an object name and returns the server address and
* the name relative to the server.
* The name parameter may be a rooted name or a relative name; an empty string
* address is returned for the latter case.
* @param {string} name The vanadium name.
* @return {Object.<string, string>} An object with the address and suffix
* split. Returned object will be in the format of:
* <pre>
* {address: string, suffix: string}
* </pre>
* Address may be in endpoint format or host:port format.
* @memberof module:vanadium.naming
*/
function splitAddressName(name) {
name = clean(name);
if (!isRooted(name)) {
return {
address: '',
suffix: name
};
}
name = name.substr(1); // trim the beginning "/"
if (name.length === 0) {
return {
address: '',
suffix: ''
};
}
if (name[0] === '@') { // <endpoint>/<suffix>
var split = _splitIntoTwo(name, '@@/');
if (split.suffix.length > 0) { // The trailing "@@" was stripped, restore
split.address = split.address + '@@';
}
return split;
}
if (name[0] === '(') { // (blessing)@host:[port]/suffix
var tmp = _splitIntoTwo(name, ')@').suffix;
var suffix = _splitIntoTwo(tmp, '/').suffix;
return {
address: _trimEnd(name, '/' + suffix),
suffix: suffix
};
}
// host:[port]/suffix
return _splitIntoTwo(name, '/');
function _splitIntoTwo(str, separator) {
var elems = str.split(separator);
return {
address: elems[0],
suffix: elems.splice(1).join(separator)
};
}
}
/**
* Gets the basename of the given vanadium name.
* @param {string} name The vanadium name.
* @return {string} The basename of the given name.
* @memberof module:vanadium.naming
*/
function basename(name) {
name = clean(name);
var split = splitAddressName(name);
if (split.suffix !== '') {
return split.suffix.substring(split.suffix.lastIndexOf('/') + 1);
} else {
return split.address;
}
}
/**
* Retrieves the parent of the given name.
* @param {string} name The vanadium name.
* @return {string | null} The parent's name or null, if there isn't one.
* @memberof module:vanadium.naming
*/
function stripBasename(name) {
name = clean(name);
var split = splitAddressName(name);
if (split.suffix !== '') {
return name.substring(0, name.lastIndexOf('/'));
} else {
return '';
}
}
// Replace every group of slashes in the string with a single slash.
function _squashMultipleSlashes(s) {
return s.replace(/\/{2,}/g, '/');
}
// Remove the last slash in the string, if any.
function _removeTailSlash(s) {
return s.replace(/\/$/g, '');
}
// Helper util that removes the given suf from the end of str
function _trimEnd(str, suf) {
var index = str.lastIndexOf(suf);
if (index + suf.length === str.length) {
return str.substring(0, index);
} else {
return str;
}
}