Alias.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { anchorIsValid } from '../doc/anchors.js';
  2. import { visit } from '../visit.js';
  3. import { ALIAS, isAlias, isCollection, isPair } from './identity.js';
  4. import { NodeBase } from './Node.js';
  5. import { toJS } from './toJS.js';
  6. class Alias extends NodeBase {
  7. constructor(source) {
  8. super(ALIAS);
  9. this.source = source;
  10. Object.defineProperty(this, 'tag', {
  11. set() {
  12. throw new Error('Alias nodes cannot have tags');
  13. }
  14. });
  15. }
  16. /**
  17. * Resolve the value of this alias within `doc`, finding the last
  18. * instance of the `source` anchor before this node.
  19. */
  20. resolve(doc) {
  21. let found = undefined;
  22. visit(doc, {
  23. Node: (_key, node) => {
  24. if (node === this)
  25. return visit.BREAK;
  26. if (node.anchor === this.source)
  27. found = node;
  28. }
  29. });
  30. return found;
  31. }
  32. toJSON(_arg, ctx) {
  33. if (!ctx)
  34. return { source: this.source };
  35. const { anchors, doc, maxAliasCount } = ctx;
  36. const source = this.resolve(doc);
  37. if (!source) {
  38. const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
  39. throw new ReferenceError(msg);
  40. }
  41. let data = anchors.get(source);
  42. if (!data) {
  43. // Resolve anchors for Node.prototype.toJS()
  44. toJS(source, null, ctx);
  45. data = anchors.get(source);
  46. }
  47. /* istanbul ignore if */
  48. if (!data || data.res === undefined) {
  49. const msg = 'This should not happen: Alias anchor was not resolved?';
  50. throw new ReferenceError(msg);
  51. }
  52. if (maxAliasCount >= 0) {
  53. data.count += 1;
  54. if (data.aliasCount === 0)
  55. data.aliasCount = getAliasCount(doc, source, anchors);
  56. if (data.count * data.aliasCount > maxAliasCount) {
  57. const msg = 'Excessive alias count indicates a resource exhaustion attack';
  58. throw new ReferenceError(msg);
  59. }
  60. }
  61. return data.res;
  62. }
  63. toString(ctx, _onComment, _onChompKeep) {
  64. const src = `*${this.source}`;
  65. if (ctx) {
  66. anchorIsValid(this.source);
  67. if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) {
  68. const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
  69. throw new Error(msg);
  70. }
  71. if (ctx.implicitKey)
  72. return `${src} `;
  73. }
  74. return src;
  75. }
  76. }
  77. function getAliasCount(doc, node, anchors) {
  78. if (isAlias(node)) {
  79. const source = node.resolve(doc);
  80. const anchor = anchors && source && anchors.get(source);
  81. return anchor ? anchor.count * anchor.aliasCount : 0;
  82. }
  83. else if (isCollection(node)) {
  84. let count = 0;
  85. for (const item of node.items) {
  86. const c = getAliasCount(doc, item, anchors);
  87. if (c > count)
  88. count = c;
  89. }
  90. return count;
  91. }
  92. else if (isPair(node)) {
  93. const kc = getAliasCount(doc, node.key, anchors);
  94. const vc = getAliasCount(doc, node.value, anchors);
  95. return Math.max(kc, vc);
  96. }
  97. return 1;
  98. }
  99. export { Alias };