UNPKG

@aws-cdk/core

Version:

AWS Cloud Development Kit Core Library

189 lines 27.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const cfn_fn_1 = require("./cfn-fn"); const token_1 = require("./token"); const util_1 = require("./util"); class Arn { /** * Creates an ARN from components. * * If `partition`, `region` or `account` are not specified, the stack's * partition, region and account will be used. * * If any component is the empty string, an empty string will be inserted * into the generated ARN at the location that component corresponds to. * * The ARN will be formatted as follows: * * arn:{partition}:{service}:{region}:{account}:{resource}{sep}{resource-name} * * The required ARN pieces that are omitted will be taken from the stack that * the 'scope' is attached to. If all ARN pieces are supplied, the supplied scope * can be 'undefined'. */ static format(components, stack) { const partition = components.partition !== undefined ? components.partition : stack.partition; const region = components.region !== undefined ? components.region : stack.region; const account = components.account !== undefined ? components.account : stack.account; const sep = components.sep !== undefined ? components.sep : '/'; const values = ['arn', ':', partition, ':', components.service, ':', region, ':', account, ':', components.resource]; if (sep !== '/' && sep !== ':' && sep !== '') { throw new Error('resourcePathSep may only be ":", "/" or an empty string'); } if (components.resourceName != null) { values.push(sep); values.push(components.resourceName); } return values.join(''); } /** * Given an ARN, parses it and returns components. * * If the ARN is a concrete string, it will be parsed and validated. The * separator (`sep`) will be set to '/' if the 6th component includes a '/', * in which case, `resource` will be set to the value before the '/' and * `resourceName` will be the rest. In case there is no '/', `resource` will * be set to the 6th components and `resourceName` will be set to the rest * of the string. * * If the ARN includes tokens (or is a token), the ARN cannot be validated, * since we don't have the actual value yet at the time of this function * call. You will have to know the separator and the type of ARN. The * resulting `ArnComponents` object will contain tokens for the * subexpressions of the ARN, not string literals. In this case this * function cannot properly parse the complete final resourceName (path) out * of ARNs that use '/' to both separate the 'resource' from the * 'resourceName' AND to subdivide the resourceName further. For example, in * S3 ARNs: * * arn:aws:s3:::my_corporate_bucket/path/to/exampleobject.png * * After parsing the resourceName will not contain * 'path/to/exampleobject.png' but simply 'path'. This is a limitation * because there is no slicing functionality in CloudFormation templates. * * @param arn The ARN to parse * @param sepIfToken The separator used to separate resource from resourceName * @param hasName Whether there is a name component in the ARN at all. For * example, SNS Topics ARNs have the 'resource' component contain the topic * name, and no 'resourceName' component. * * @returns an ArnComponents object which allows access to the various * components of the ARN. * * @returns an ArnComponents object which allows access to the various * components of the ARN. */ static parse(arn, sepIfToken = '/', hasName = true) { if (token_1.Token.isUnresolved(arn)) { return parseToken(arn, sepIfToken, hasName); } const components = arn.split(':'); if (components.length < 6) { throw new Error('ARNs must have at least 6 components: ' + arn); } const [arnPrefix, partition, service, region, account, sixth, ...rest] = components; if (arnPrefix !== 'arn') { throw new Error('ARNs must start with "arn:": ' + arn); } if (!service) { throw new Error('The `service` component (3rd component) is required: ' + arn); } if (!sixth) { throw new Error('The `resource` component (6th component) is required: ' + arn); } let resource; let resourceName; let sep; let sepIndex = sixth.indexOf('/'); if (sepIndex !== -1) { sep = '/'; } else if (rest.length > 0) { sep = ':'; sepIndex = -1; } if (sepIndex !== -1) { resource = sixth.substr(0, sepIndex); resourceName = sixth.substr(sepIndex + 1); } else { resource = sixth; } if (rest.length > 0) { if (!resourceName) { resourceName = ''; } else { resourceName += ':'; } resourceName += rest.join(':'); } // "|| undefined" will cause empty strings to be treated as "undefined" return util_1.filterUndefined({ service: service || undefined, resource: resource || undefined, partition: partition || undefined, region: region || undefined, account: account || undefined, resourceName, sep }); } constructor() { } } exports.Arn = Arn; /** * Given a Token evaluating to ARN, parses it and returns components. * * The ARN cannot be validated, since we don't have the actual value yet * at the time of this function call. You will have to know the separator * and the type of ARN. * * The resulting `ArnComponents` object will contain tokens for the * subexpressions of the ARN, not string literals. * * WARNING: this function cannot properly parse the complete final * resourceName (path) out of ARNs that use '/' to both separate the * 'resource' from the 'resourceName' AND to subdivide the resourceName * further. For example, in S3 ARNs: * * arn:aws:s3:::my_corporate_bucket/path/to/exampleobject.png * * After parsing the resourceName will not contain 'path/to/exampleobject.png' * but simply 'path'. This is a limitation because there is no slicing * functionality in CloudFormation templates. * * @param arnToken The input token that contains an ARN * @param sep The separator used to separate resource from resourceName * @param hasName Whether there is a name component in the ARN at all. * For example, SNS Topics ARNs have the 'resource' component contain the * topic name, and no 'resourceName' component. * @returns an ArnComponents object which allows access to the various * components of the ARN. */ function parseToken(arnToken, sep = '/', hasName = true) { // Arn ARN looks like: // arn:partition:service:region:account-id:resource // arn:partition:service:region:account-id:resourcetype/resource // arn:partition:service:region:account-id:resourcetype:resource // We need the 'hasName' argument because {Fn::Select}ing a nonexistent field // throws an error. const components = cfn_fn_1.Fn.split(':', arnToken); const partition = cfn_fn_1.Fn.select(1, components).toString(); const service = cfn_fn_1.Fn.select(2, components).toString(); const region = cfn_fn_1.Fn.select(3, components).toString(); const account = cfn_fn_1.Fn.select(4, components).toString(); if (sep === ':') { const resource = cfn_fn_1.Fn.select(5, components).toString(); const resourceName = hasName ? cfn_fn_1.Fn.select(6, components).toString() : undefined; return { partition, service, region, account, resource, resourceName, sep }; } else { const lastComponents = cfn_fn_1.Fn.split(sep, cfn_fn_1.Fn.select(5, components)); const resource = cfn_fn_1.Fn.select(0, lastComponents).toString(); const resourceName = hasName ? cfn_fn_1.Fn.select(1, lastComponents).toString() : undefined; return { partition, service, region, account, resource, resourceName, sep }; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXJuLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiYXJuLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEscUNBQThCO0FBRTlCLG1DQUFnQztBQUNoQyxpQ0FBeUM7QUF5RHpDLE1BQWEsR0FBRztJQUNkOzs7Ozs7Ozs7Ozs7Ozs7O09BZ0JHO0lBQ0ksTUFBTSxDQUFDLE1BQU0sQ0FBQyxVQUF5QixFQUFFLEtBQVk7UUFDMUQsTUFBTSxTQUFTLEdBQUcsVUFBVSxDQUFDLFNBQVMsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUM7UUFDOUYsTUFBTSxNQUFNLEdBQUcsVUFBVSxDQUFDLE1BQU0sS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUM7UUFDbEYsTUFBTSxPQUFPLEdBQUcsVUFBVSxDQUFDLE9BQU8sS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUM7UUFDdEYsTUFBTSxHQUFHLEdBQUcsVUFBVSxDQUFDLEdBQUcsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQztRQUVoRSxNQUFNLE1BQU0sR0FBRyxDQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRSxVQUFVLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxHQUFHLEVBQUUsVUFBVSxDQUFDLFFBQVEsQ0FBRSxDQUFDO1FBRXZILElBQUksR0FBRyxLQUFLLEdBQUcsSUFBSSxHQUFHLEtBQUssR0FBRyxJQUFJLEdBQUcsS0FBSyxFQUFFLEVBQUU7WUFDNUMsTUFBTSxJQUFJLEtBQUss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QUM7UUFFL0UsT0FBTyxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsWUFBWSxFQUFFLEdBQUcsRUFBRSxDQUFDO0tBQzdFO1NBQU07UUFDTCxNQUFNLGNBQWMsR0FBRyxXQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxXQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDO1FBRS9ELE1BQU0sUUFBUSxHQUFHLFdBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLGNBQWMsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ3pELE1BQU0sWUFBWSxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUMsV0FBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEVBQUUsY0FBYyxDQUFDLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztRQUVuRixPQUFPLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxZQUFZLEVBQUUsR0FBRyxFQUFFLENBQUM7S0FDN0U7QUFDSCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRm4gfSBmcm9tICcuL2Nmbi1mbic7XG5pbXBvcnQgeyBTdGFjayB9IGZyb20gJy4vc3RhY2snO1xuaW1wb3J0IHsgVG9rZW4gfSBmcm9tICcuL3Rva2VuJztcbmltcG9ydCB7IGZpbHRlclVuZGVmaW5lZCB9IGZyb20gJy4vdXRpbCc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgQXJuQ29tcG9uZW50cyB7XG4gIC8qKlxuICAgKiBUaGUgcGFydGl0aW9uIHRoYXQgdGhlIHJlc291cmNlIGlzIGluLiBGb3Igc3RhbmRhcmQgQVdTIHJlZ2lvbnMsIHRoZVxuICAgKiBwYXJ0aXRpb24gaXMgYXdzLiBJZiB5b3UgaGF2ZSByZXNvdXJjZXMgaW4gb3RoZXIgcGFydGl0aW9ucywgdGhlXG4gICAqIHBhcnRpdGlvbiBpcyBhd3MtcGFydGl0aW9ubmFtZS4gRm9yIGV4YW1wbGUsIHRoZSBwYXJ0aXRpb24gZm9yIHJlc291cmNlc1xuICAgKiBpbiB0aGUgQ2hpbmEgKEJlaWppbmcpIHJlZ2lvbiBpcyBhd3MtY24uXG4gICAqXG4gICAqIEBkZWZhdWx0IFRoZSBBV1MgcGFydGl0aW9uIHRoZSBzdGFjayBpcyBkZXBsb3llZCB0by5cbiAgICovXG4gIHJlYWRvbmx5IHBhcnRpdGlvbj86IHN0cmluZztcblxuICAvKipcbiAgICogVGhlIHNlcnZpY2UgbmFtZXNwYWNlIHRoYXQgaWRlbnRpZmllcyB0aGUgQVdTIHByb2R1Y3QgKGZvciBleGFtcGxlLFxuICAgKiAnczMnLCAnaWFtJywgJ2NvZGVwaXBsaW5lJykuXG4gICAqL1xuICByZWFkb25seSBzZXJ2aWNlOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFRoZSByZWdpb24gdGhlIHJlc291cmNlIHJlc2lkZXMgaW4uIE5vdGUgdGhhdCB0aGUgQVJOcyBmb3Igc29tZSByZXNvdXJjZXNcbiAgICogZG8gbm90IHJlcXVpcmUgYSByZWdpb24sIHNvIHRoaXMgY29tcG9uZW50IG1pZ2h0IGJlIG9taXR0ZWQuXG4gICAqXG4gICAqIEBkZWZhdWx0IFRoZSByZWdpb24gdGhlIHN0YWNrIGlzIGRlcGxveWVkIHRvLlxuICAgKi9cbiAgcmVhZG9ubHkgcmVnaW9uPzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBUaGUgSUQgb2YgdGhlIEFXUyBhY2NvdW50IHRoYXQgb3ducyB0aGUgcmVzb3VyY2UsIHdpdGhvdXQgdGhlIGh5cGhlbnMuXG4gICAqIEZvciBleGFtcGxlLCAxMjM0NTY3ODkwMTIuIE5vdGUgdGhhdCB0aGUgQVJOcyBmb3Igc29tZSByZXNvdXJjZXMgZG9uJ3RcbiAgICogcmVxdWlyZSBhbiBhY2NvdW50IG51bWJlciwgc28gdGhpcyBjb21wb25lbnQgbWlnaHQgYmUgb21pdHRlZC5cbiAgICpcbiAgICogQGRlZmF1bHQgVGhlIGFjY291bnQgdGhlIHN0YWNrIGlzIGRlcGxveWVkIHRvLlxuICAgKi9cbiAgcmVhZG9ubHkgYWNjb3VudD86IHN0cmluZztcblxuICAvKipcbiAgICogUmVzb3VyY2UgdHlwZSAoZS5nLiBcInRhYmxlXCIsIFwiYXV0b1NjYWxpbmdHcm91cFwiLCBcImNlcnRpZmljYXRlXCIpLlxuICAgKiBGb3Igc29tZSByZXNvdXJjZSB0eXBlcywgZS5nLiBTMyBidWNrZXRzLCB0aGlzIGZpZWxkIGRlZmluZXMgdGhlIGJ1Y2tldCBuYW1lLlxuICAgKi9cbiAgcmVhZG9ubHkgcmVzb3VyY2U6IHN0cmluZztcblxuICAvKipcbiAgICogU2VwYXJhdG9yIGJldHdlZW4gcmVzb3VyY2UgdHlwZSBhbmQgdGhlIHJlc291cmNlLlxuICAgKlxuICAgKiBDYW4gYmUgZWl0aGVyICcvJywgJzonIG9yIGFuIGVtcHR5IHN0cmluZy4gV2lsbCBvbmx5IGJlIHVzZWQgaWYgcmVzb3VyY2VOYW1lIGlzIGRlZmluZWQuXG4gICAqIEBkZWZhdWx0ICcvJ1xuICAgKi9cbiAgcmVhZG9ubHkgc2VwPzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBSZXNvdXJjZSBuYW1lIG9yIHBhdGggd2l0aGluIHRoZSByZXNvdXJjZSAoaS5lLiBTMyBidWNrZXQgb2JqZWN0IGtleSkgb3JcbiAgICogYSB3aWxkY2FyZCBzdWNoIGFzIGBgXCIqXCJgYC4gVGhpcyBpcyBzZXJ2aWNlLWRlcGVuZGVudC5cbiAgICovXG4gIHJlYWRvbmx5IHJlc291cmNlTmFtZT86IHN0cmluZztcbn1cblxuZXhwb3J0IGNsYXNzIEFybiB7XG4gIC8qKlxuICAgKiBDcmVhdGVzIGFuIEFSTiBmcm9tIGNvbXBvbmVudHMuXG4gICAqXG4gICAqIElmIGBwYXJ0aXRpb25gLCBgcmVnaW9uYCBvciBgYWNjb3VudGAgYXJlIG5vdCBzcGVjaWZpZWQsIHRoZSBzdGFjaydzXG4gICAqIHBhcnRpdGlvbiwgcmVnaW9uIGFuZCBhY2NvdW50IHdpbGwgYmUgdXNlZC5cbiAgICpcbiAgICogSWYgYW55IGNvbXBvbmVudCBpcyB0aGUgZW1wdHkgc3RyaW5nLCBhbiBlbXB0eSBzdHJpbmcgd2lsbCBiZSBpbnNlcnRlZFxuICAgKiBpbnRvIHRoZSBnZW5lcmF0ZWQgQVJOIGF0IHRoZSBsb2NhdGlvbiB0aGF0IGNvbXBvbmVudCBjb3JyZXNwb25kcyB0by5cbiAgICpcbiAgICogVGhlIEFSTiB3aWxsIGJlIGZvcm1hdHRlZCBhcyBmb2xsb3dzOlxuICAgKlxuICAgKiAgIGFybjp7cGFydGl0aW9ufTp7c2VydmljZX06e3JlZ2lvbn06e2FjY291bnR9OntyZXNvdXJjZX17c2VwfXtyZXNvdXJjZS1uYW1lfVxuICAgKlxuICAgKiBUaGUgcmVxdWlyZWQgQVJOIHBpZWNlcyB0aGF0IGFyZSBvbWl0dGVkIHdpbGwgYmUgdGFrZW4gZnJvbSB0aGUgc3RhY2sgdGhhdFxuICAgKiB0aGUgJ3Njb3BlJyBpcyBhdHRhY2hlZCB0by4gSWYgYWxsIEFSTiBwaWVjZXMgYXJlIHN1cHBsaWVkLCB0aGUgc3VwcGxpZWQgc2NvcGVcbiAgICogY2FuIGJlICd1bmRlZmluZWQnLlxuICAgKi9cbiAgcHVibGljIHN0YXRpYyBmb3JtYXQoY29tcG9uZW50czogQXJuQ29tcG9uZW50cywgc3RhY2s6IFN0YWNrKTogc3RyaW5nIHtcbiAgICBjb25zdCBwYXJ0aXRpb24gPSBjb21wb25lbnRzLnBhcnRpdGlvbiAhPT0gdW5kZWZpbmVkID8gY29tcG9uZW50cy5wYXJ0aXRpb24gOiBzdGFjay5wYXJ0aXRpb247XG4gICAgY29uc3QgcmVnaW9uID0gY29tcG9uZW50cy5yZWdpb24gIT09IHVuZGVmaW5lZCA/IGNvbXBvbmVudHMucmVnaW9uIDogc3RhY2sucmVnaW9uO1xuICAgIGNvbnN0IGFjY291bnQgPSBjb21wb25lbnRzLmFjY291bnQgIT09IHVuZGVmaW5lZCA/IGNvbXBvbmVudHMuYWNjb3VudCA6IHN0YWNrLmFjY291bnQ7XG4gICAgY29uc3Qgc2VwID0gY29tcG9uZW50cy5zZXAgIT09IHVuZGVmaW5lZCA/IGNvbXBvbmVudHMuc2VwIDogJy8nO1xuXG4gICAgY29uc3QgdmFsdWVzID0gWyAnYXJuJywgJzonLCBwYXJ0aXRpb24sICc6JywgY29tcG9uZW50cy5zZXJ2aWNlLCAnOicsIHJlZ2lvbiwgJzonLCBhY2NvdW50LCAnOicsIGNvbXBvbmVudHMucmVzb3VyY2UgXTtcblxuICAgIGlmIChzZXAgIT09ICcvJyAmJiBzZXAgIT09ICc6JyAmJiBzZXAgIT09ICcnKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ3Jlc291cmNlUGF0aFNlcCBtYXkgb25seSBiZSBcIjpcIiwgXCIvXCIgb3IgYW4gZW1wdHkgc3RyaW5nJyk7XG4gICAgfVxuXG4gICAgaWYgKGNvbXBvbmVudHMucmVzb3VyY2VOYW1lICE9IG51bGwpIHtcbiAgICAgIHZhbHVlcy5wdXNoKHNlcCk7XG4gICAgICB2YWx1ZXMucHVzaChjb21wb25lbnRzLnJlc291cmNlTmFtZSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHZhbHVlcy5qb2luKCcnKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHaXZlbiBhbiBBUk4sIHBhcnNlcyBpdCBhbmQgcmV0dXJucyBjb21wb25lbnRzLlxuICAgKlxuICAgKiBJZiB0aGUgQVJOIGlzIGEgY29uY3JldGUgc3RyaW5nLCBpdCB3aWxsIGJlIHBhcnNlZCBhbmQgdmFsaWRhdGVkLiBUaGVcbiAgICogc2VwYXJhdG9yIChgc2VwYCkgd2lsbCBiZSBzZXQgdG8gJy8nIGlmIHRoZSA2dGggY29tcG9uZW50IGluY2x1ZGVzIGEgJy8nLFxuICAgKiBpbiB3aGljaCBjYXNlLCBgcmVzb3VyY2VgIHdpbGwgYmUgc2V0IHRvIHRoZSB2YWx1ZSBiZWZvcmUgdGhlICcvJyBhbmRcbiAgICogYHJlc291cmNlTmFtZWAgd2lsbCBiZSB0aGUgcmVzdC4gSW4gY2FzZSB0aGVyZSBpcyBubyAnLycsIGByZXNvdXJjZWAgd2lsbFxuICAgKiBiZSBzZXQgdG8gdGhlIDZ0aCBjb21wb25lbnRzIGFuZCBgcmVzb3VyY2VOYW1lYCB3aWxsIGJlIHNldCB0byB0aGUgcmVzdFxuICAgKiBvZiB0aGUgc3RyaW5nLlxuICAgKlxuICAgKiBJZiB0aGUgQVJOIGluY2x1ZGVzIHRva2VucyAob3IgaXMgYSB0b2tlbiksIHRoZSBBUk4gY2Fubm90IGJlIHZhbGlkYXRlZCxcbiAgICogc2luY2Ugd2UgZG9uJ3QgaGF2ZSB0aGUgYWN0dWFsIHZhbHVlIHlldCBhdCB0aGUgdGltZSBvZiB0aGlzIGZ1bmN0aW9uXG4gICAqIGNhbGwuIFlvdSB3aWxsIGhhdmUgdG8ga25vdyB0aGUgc2VwYXJhdG9yIGFuZCB0aGUgdHlwZSBvZiBBUk4uIFRoZVxuICAgKiByZXN1bHRpbmcgYEFybkNvbXBvbmVudHNgIG9iamVjdCB3aWxsIGNvbnRhaW4gdG9rZW5zIGZvciB0aGVcbiAgICogc3ViZXhwcmVzc2lvbnMgb2YgdGhlIEFSTiwgbm90IHN0cmluZyBsaXRlcmFscy4gSW4gdGhpcyBjYXNlIHRoaXNcbiAgICogZnVuY3Rpb24gY2Fubm90IHByb3Blcmx5IHBhcnNlIHRoZSBjb21wbGV0ZSBmaW5hbCByZXNvdXJjZU5hbWUgKHBhdGgpIG91dFxuICAgKiBvZiBBUk5zIHRoYXQgdXNlICcvJyB0byBib3RoIHNlcGFyYXRlIHRoZSAncmVzb3VyY2UnIGZyb20gdGhlXG4gICAqICdyZXNvdXJjZU5hbWUnIEFORCB0byBzdWJkaXZpZGUgdGhlIHJlc291cmNlTmFtZSBmdXJ0aGVyLiBGb3IgZXhhbXBsZSwgaW5cbiAgICogUzMgQVJOczpcbiAgICpcbiAgICogICAgYXJuOmF3czpzMzo6Om15X2NvcnBvcmF0ZV9idWNrZXQvcGF0aC90by9leGFtcGxlb2JqZWN0LnBuZ1xuICAgKlxuICAgKiBBZnRlciBwYXJzaW5nIHRoZSByZXNvdXJjZU5hbWUgd2lsbCBub3QgY29udGFpblxuICAgKiAncGF0aC90by9leGFtcGxlb2JqZWN0LnBuZycgYnV0IHNpbXBseSAncGF0aCcuIFRoaXMgaXMgYSBsaW1pdGF0aW9uXG4gICAqIGJlY2F1c2UgdGhlcmUgaXMgbm8gc2xpY2luZyBmdW5jdGlvbmFsaXR5IGluIENsb3VkRm9ybWF0aW9uIHRlbXBsYXRlcy5cbiAgICpcbiAgICogQHBhcmFtIGFybiBUaGUgQVJOIHRvIHBhcnNlXG4gICAqIEBwYXJhbSBzZXBJZlRva2VuIFRoZSBzZXBhcmF0b3IgdXNlZCB0byBzZXBhcmF0ZSByZXNvdXJjZSBmcm9tIHJlc291cmNlTmFtZVxuICAgKiBAcGFyYW0gaGFzTmFtZSBXaGV0aGVyIHRoZXJlIGlzIGEgbmFtZSBjb21wb25lbnQgaW4gdGhlIEFSTiBhdCBhbGwuIEZvclxuICAgKiBleGFtcGxlLCBTTlMgVG9waWNzIEFSTnMgaGF2ZSB0aGUgJ3Jlc291cmNlJyBjb21wb25lbnQgY29udGFpbiB0aGUgdG9waWNcbiAgICogbmFtZSwgYW5kIG5vICdyZXNvdXJjZU5hbWUnIGNvbXBvbmVudC5cbiAgICpcbiAgICogQHJldHVybnMgYW4gQXJuQ29tcG9uZW50cyBvYmplY3Qgd2hpY2ggYWxsb3dzIGFjY2VzcyB0byB0aGUgdmFyaW91c1xuICAgKiBjb21wb25lbnRzIG9mIHRoZSBBUk4uXG4gICAqXG4gICAqIEByZXR1cm5zIGFuIEFybkNvbXBvbmVudHMgb2JqZWN0IHdoaWNoIGFsbG93cyBhY2Nlc3MgdG8gdGhlIHZhcmlvdXNcbiAgICogICAgICBjb21wb25lbnRzIG9mIHRoZSBBUk4uXG4gICAqL1xuICBwdWJsaWMgc3RhdGljIHBhcnNlKGFybjogc3RyaW5nLCBzZXBJZlRva2VuOiBzdHJpbmcgPSAnLycsIGhhc05hbWU6IGJvb2xlYW4gPSB0cnVlKTogQXJuQ29tcG9uZW50cyB7XG4gICAgaWYgKFRva2VuLmlzVW5yZXNvbHZlZChhcm4pKSB7XG4gICAgICByZXR1cm4gcGFyc2VUb2tlbihhcm4sIHNlcElmVG9rZW4sIGhhc05hbWUpO1xuICAgIH1cblxuICAgIGNvbnN0IGNvbXBvbmVudHMgPSBhcm4uc3BsaXQoJzonKSBhcyBBcnJheTxzdHJpbmcgfCB1bmRlZmluZWQ+O1xuXG4gICAgaWYgKGNvbXBvbmVudHMubGVuZ3RoIDwgNikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdBUk5zIG11c3QgaGF2ZSBhdCBsZWFzdCA2IGNvbXBvbmVudHM6ICcgKyBhcm4pO1xuICAgIH1cblxuICAgIGNvbnN0IFsgYXJuUHJlZml4LCBwYXJ0aXRpb24sIHNlcnZpY2UsIHJlZ2lvbiwgYWNjb3VudCwgc2l4dGgsIC4uLnJlc3QgXSA9IGNvbXBvbmVudHM7XG5cbiAgICBpZiAoYXJuUHJlZml4ICE9PSAnYXJuJykge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdBUk5zIG11c3Qgc3RhcnQgd2l0aCBcImFybjpcIjogJyArIGFybik7XG4gICAgfVxuXG4gICAgaWYgKCFzZXJ2aWNlKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1RoZSBgc2VydmljZWAgY29tcG9uZW50ICgzcmQgY29tcG9uZW50KSBpcyByZXF1aXJlZDogJyArIGFybik7XG4gICAgfVxuXG4gICAgaWYgKCFzaXh0aCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdUaGUgYHJlc291cmNlYCBjb21wb25lbnQgKDZ0aCBjb21wb25lbnQpIGlzIHJlcXVpcmVkOiAnICsgYXJuKTtcbiAgICB9XG5cbiAgICBsZXQgcmVzb3VyY2U6IHN0cmluZztcbiAgICBsZXQgcmVzb3VyY2VOYW1lOiBzdHJpbmcgfCB1bmRlZmluZWQ7XG4gICAgbGV0IHNlcDogc3RyaW5nIHwgdW5kZWZpbmVkO1xuXG4gICAgbGV0IHNlcEluZGV4ID0gc2l4dGguaW5kZXhPZignLycpO1xuICAgIGlmIChzZXBJbmRleCAhPT0gLTEpIHtcbiAgICAgIHNlcCA9ICcvJztcbiAgICB9IGVsc2UgaWYgKHJlc3QubGVuZ3RoID4gMCkge1xuICAgICAgc2VwID0gJzonO1xuICAgICAgc2VwSW5kZXggPSAtMTtcbiAgICB9XG5cbiAgICBpZiAoc2VwSW5kZXggIT09IC0xKSB7XG4gICAgICByZXNvdXJjZSA9IHNpeHRoLnN1YnN0cigwLCBzZXBJbmRleCk7XG4gICAgICByZXNvdXJjZU5hbWUgPSBzaXh0aC5zdWJzdHIoc2VwSW5kZXggKyAxKTtcbiAgICB9IGVsc2Uge1xuICAgICAgcmVzb3VyY2UgPSBzaXh0aDtcbiAgICB9XG5cbiAgICBpZiAocmVzdC5sZW5ndGggPiAwKSB7XG4gICAgICBpZiAoIXJlc291cmNlTmFtZSkge1xuICAgICAgICByZXNvdXJjZU5hbWUgPSAnJztcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJlc291cmNlTmFtZSArPSAnOic7XG4gICAgICB9XG5cbiAgICAgIHJlc291cmNlTmFtZSArPSByZXN0LmpvaW4oJzonKTtcbiAgICB9XG5cbiAgICAvLyBcInx8IHVuZGVmaW5lZFwiIHdpbGwgY2F1c2UgZW1wdHkgc3RyaW5ncyB0byBiZSB0cmVhdGVkIGFzIFwidW5kZWZpbmVkXCJcbiAgICByZXR1cm4gZmlsdGVyVW5kZWZpbmVkKHtcbiAgICAgIHNlcnZpY2U6IHNlcnZpY2UgfHwgdW5kZWZpbmVkLFxuICAgICAgcmVzb3VyY2U6IHJlc291cmNlIHx8IHVuZGVmaW5lZCAsXG4gICAgICBwYXJ0aXRpb246IHBhcnRpdGlvbiB8fCB1bmRlZmluZWQsXG4gICAgICByZWdpb246IHJlZ2lvbiB8fCB1bmRlZmluZWQsXG4gICAgICBhY2NvdW50OiBhY2NvdW50IHx8IHVuZGVmaW5lZCxcbiAgICAgIHJlc291cmNlTmFtZSxcbiAgICAgIHNlcFxuICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSBjb25zdHJ1Y3RvcigpIHsgfVxufVxuXG4vKipcbiAqIEdpdmVuIGEgVG9rZW4gZXZhbHVhdGluZyB0byBBUk4sIHBhcnNlcyBpdCBhbmQgcmV0dXJucyBjb21wb25lbnRzLlxuICpcbiAqIFRoZSBBUk4gY2Fubm90IGJlIHZhbGlkYXRlZCwgc2luY2Ugd2UgZG9uJ3QgaGF2ZSB0aGUgYWN0dWFsIHZhbHVlIHlldFxuICogYXQgdGhlIHRpbWUgb2YgdGhpcyBmdW5jdGlvbiBjYWxsLiBZb3Ugd2lsbCBoYXZlIHRvIGtub3cgdGhlIHNlcGFyYXRvclxuICogYW5kIHRoZSB0eXBlIG9mIEFSTi5cbiAqXG4gKiBUaGUgcmVzdWx0aW5nIGBBcm5Db21wb25lbnRzYCBvYmplY3Qgd2lsbCBjb250YWluIHRva2VucyBmb3IgdGhlXG4gKiBzdWJleHByZXNzaW9ucyBvZiB0aGUgQVJOLCBub3Qgc3RyaW5nIGxpdGVyYWxzLlxuICpcbiAqIFdBUk5JTkc6IHRoaXMgZnVuY3Rpb24gY2Fubm90IHByb3Blcmx5IHBhcnNlIHRoZSBjb21wbGV0ZSBmaW5hbFxuICogcmVzb3VyY2VOYW1lIChwYXRoKSBvdXQgb2YgQVJOcyB0aGF0IHVzZSAnLycgdG8gYm90aCBzZXBhcmF0ZSB0aGVcbiAqICdyZXNvdXJjZScgZnJvbSB0aGUgJ3Jlc291cmNlTmFtZScgQU5EIHRvIHN1YmRpdmlkZSB0aGUgcmVzb3VyY2VOYW1lXG4gKiBmdXJ0aGVyLiBGb3IgZXhhbXBsZSwgaW4gUzMgQVJOczpcbiAqXG4gKiAgICBhcm46YXdzOnMzOjo6bXlfY29ycG9yYXRlX2J1Y2tldC9wYXRoL3RvL2V4YW1wbGVvYmplY3QucG5nXG4gKlxuICogQWZ0ZXIgcGFyc2luZyB0aGUgcmVzb3VyY2VOYW1lIHdpbGwgbm90IGNvbnRhaW4gJ3BhdGgvdG8vZXhhbXBsZW9iamVjdC5wbmcnXG4gKiBidXQgc2ltcGx5ICdwYXRoJy4gVGhpcyBpcyBhIGxpbWl0YXRpb24gYmVjYXVzZSB0aGVyZSBpcyBubyBzbGljaW5nXG4gKiBmdW5jdGlvbmFsaXR5IGluIENsb3VkRm9ybWF0aW9uIHRlbXBsYXRlcy5cbiAqXG4gKiBAcGFyYW0gYXJuVG9rZW4gVGhlIGlucHV0IHRva2VuIHRoYXQgY29udGFpbnMgYW4gQVJOXG4gKiBAcGFyYW0gc2VwIFRoZSBzZXBhcmF0b3IgdXNlZCB0byBzZXBhcmF0ZSByZXNvdXJjZSBmcm9tIHJlc291cmNlTmFtZVxuICogQHBhcmFtIGhhc05hbWUgV2hldGhlciB0aGVyZSBpcyBhIG5hbWUgY29tcG9uZW50IGluIHRoZSBBUk4gYXQgYWxsLlxuICogRm9yIGV4YW1wbGUsIFNOUyBUb3BpY3MgQVJOcyBoYXZlIHRoZSAncmVzb3VyY2UnIGNvbXBvbmVudCBjb250YWluIHRoZVxuICogdG9waWMgbmFtZSwgYW5kIG5vICdyZXNvdXJjZU5hbWUnIGNvbXBvbmVudC5cbiAqIEByZXR1cm5zIGFuIEFybkNvbXBvbmVudHMgb2JqZWN0IHdoaWNoIGFsbG93cyBhY2Nlc3MgdG8gdGhlIHZhcmlvdXNcbiAqIGNvbXBvbmVudHMgb2YgdGhlIEFSTi5cbiAqL1xuZnVuY3Rpb24gcGFyc2VUb2tlbihhcm5Ub2tlbjogc3RyaW5nLCBzZXA6IHN0cmluZyA9ICcvJywgaGFzTmFtZTogYm9vbGVhbiA9IHRydWUpOiBBcm5Db21wb25lbnRzIHtcbiAgLy8gQXJuIEFSTiBsb29rcyBsaWtlOlxuICAvLyBhcm46cGFydGl0aW9uOnNlcnZpY2U6cmVnaW9uOmFjY291bnQtaWQ6cmVzb3VyY2VcbiAgLy8gYXJuOnBhcnRpdGlvbjpzZXJ2aWNlOnJlZ2lvbjphY2NvdW50LWlkOnJlc291cmNldHlwZS9yZXNvdXJjZVxuICAvLyBhcm46cGFydGl0aW9uOnNlcnZpY2U6cmVnaW9uOmFjY291bnQtaWQ6cmVzb3VyY2V0eXBlOnJlc291cmNlXG5cbiAgLy8gV2UgbmVlZCB0aGUgJ2hhc05hbWUnIGFyZ3VtZW50IGJlY2F1c2Uge0ZuOjpTZWxlY3R9aW5nIGEgbm9uZXhpc3RlbnQgZmllbGRcbiAgLy8gdGhyb3dzIGFuIGVycm9yLlxuXG4gIGNvbnN0IGNvbXBvbmVudHMgPSBGbi5zcGxpdCgnOicsIGFyblRva2VuKTtcblxuICBjb25zdCBwYXJ0aXRpb24gPSBGbi5zZWxlY3QoMSwgY29tcG9uZW50cykudG9TdHJpbmcoKTtcbiAgY29uc3Qgc2VydmljZSA9IEZuLnNlbGVjdCgyLCBjb21wb25lbnRzKS50b1N0cmluZygpO1xuICBjb25zdCByZWdpb24gPSBGbi5zZWxlY3QoMywgY29tcG9uZW50cykudG9TdHJpbmcoKTtcbiAgY29uc3QgYWNjb3VudCA9IEZuLnNlbGVjdCg0LCBjb21wb25lbnRzKS50b1N0cmluZygpO1xuXG4gIGlmIChzZXAgPT09ICc6Jykge1xuICAgIGNvbnN0IHJlc291cmNlID0gRm4uc2VsZWN0KDUsIGNvbXBvbmVudHMpLnRvU3RyaW5nKCk7XG4gICAgY29uc3QgcmVzb3VyY2VOYW1lID0gaGFzTmFtZSA/IEZuLnNlbGVjdCg2LCBjb21wb25lbnRzKS50b1N0cmluZygpIDogdW5kZWZpbmVkO1xuXG4gICAgcmV0dXJuIHsgcGFydGl0aW9uLCBzZXJ2aWNlLCByZWdpb24sIGFjY291bnQsIHJlc291cmNlLCByZXNvdXJjZU5hbWUsIHNlcCB9O1xuICB9IGVsc2Uge1xuICAgIGNvbnN0IGxhc3RDb21wb25lbnRzID0gRm4uc3BsaXQoc2VwLCBGbi5zZWxlY3QoNSwgY29tcG9uZW50cykpO1xuXG4gICAgY29uc3QgcmVzb3VyY2UgPSBGbi5zZWxlY3QoMCwgbGFzdENvbXBvbmVudHMpLnRvU3RyaW5nKCk7XG4gICAgY29uc3QgcmVzb3VyY2VOYW1lID0gaGFzTmFtZSA/IEZuLnNlbGVjdCgxLCBsYXN0Q29tcG9uZW50cykudG9TdHJpbmcoKSA6IHVuZGVmaW5lZDtcblxuICAgIHJldHVybiB7IHBhcnRpdGlvbiwgc2VydmljZSwgcmVnaW9uLCBhY2NvdW50LCByZXNvdXJjZSwgcmVzb3VyY2VOYW1lLCBzZXAgfTtcbiAgfVxufVxuIl19