Skip to content

Commit 53a27fc

Browse files
authored
fix: Harden parser input handling (#2240)
1 parent c9b6a2d commit 53a27fc

10 files changed

Lines changed: 222 additions & 49 deletions

File tree

cli/lib/tsd-jsdoc/publish.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ var keepTags = [
138138
"param",
139139
"returns",
140140
"throws",
141-
"see"
141+
"see",
142+
"deprecated"
142143
];
143144

144145
// parses a comment into text and tags

index.d.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -721,10 +721,11 @@ export class Namespace extends NamespaceBase {
721721
* Constructs a namespace from JSON.
722722
* @param name Namespace name
723723
* @param json JSON object
724+
* @param [depth] Current nesting depth, defaults to `0`
724725
* @returns Created namespace
725726
* @throws {TypeError} If arguments are invalid
726727
*/
727-
public static fromJSON(name: string, json: { [k: string]: any }): Namespace;
728+
public static fromJSON(name: string, json: { [k: string]: any }, depth?: number): Namespace;
728729

729730
/**
730731
* Converts an array of reflection objects to JSON.
@@ -776,9 +777,10 @@ export abstract class NamespaceBase extends ReflectionObject {
776777
/**
777778
* Adds nested objects to this namespace from nested object descriptors.
778779
* @param nestedJson Any nested object descriptors
780+
* @param [depth] Current nesting depth, defaults to `0`
779781
* @returns `this`
780782
*/
781-
public addJSON(nestedJson: { [k: string]: AnyNestedObject }): Namespace;
783+
public addJSON(nestedJson: { [k: string]: AnyNestedObject }, depth?: number): Namespace;
782784

783785
/**
784786
* Gets the nested object of the specified name.
@@ -1350,9 +1352,10 @@ export class Root extends NamespaceBase {
13501352
* Loads a namespace descriptor into a root namespace.
13511353
* @param json Namespace descriptor
13521354
* @param [root] Root namespace, defaults to create a new one if omitted
1355+
* @param [depth] Current nesting depth, defaults to `0`
13531356
* @returns Root namespace
13541357
*/
1355-
public static fromJSON(json: INamespace, root?: Root): Root;
1358+
public static fromJSON(json: INamespace, root?: Root, depth?: number): Root;
13561359

13571360
/**
13581361
* Resolves the path of an imported file, relative to the importing origin.
@@ -1503,10 +1506,11 @@ export class Service extends NamespaceBase {
15031506
* Constructs a service from a service descriptor.
15041507
* @param name Service name
15051508
* @param json Service descriptor
1509+
* @param [depth] Current nesting depth, defaults to `0`
15061510
* @returns Created service
15071511
* @throws {TypeError} If arguments are invalid
15081512
*/
1509-
public static fromJSON(name: string, json: IService): Service;
1513+
public static fromJSON(name: string, json: IService, depth?: number): Service;
15101514

15111515
/**
15121516
* Converts this service to a service descriptor.
@@ -1657,9 +1661,10 @@ export class Type extends NamespaceBase {
16571661
* Creates a message type from a message type descriptor.
16581662
* @param name Message name
16591663
* @param json Message type descriptor
1664+
* @param [depth] Current nesting depth, defaults to `0`
16601665
* @returns Created message type
16611666
*/
1662-
public static fromJSON(name: string, json: IType): Type;
1667+
public static fromJSON(name: string, json: IType, depth?: number): Type;
16631668

16641669
/**
16651670
* Converts this message type to a message type descriptor.
@@ -2569,6 +2574,14 @@ export namespace util {
25692574
/** Node's fs module if available. */
25702575
let fs: { [k: string]: any };
25712576

2577+
/**
2578+
* Checks a recursion depth.
2579+
* @param depth Depth of recursion
2580+
* @returns Depth of recursion
2581+
* @throws {Error} If depth exceeds util.recursionLimit
2582+
*/
2583+
function checkDepth(depth: (number|undefined)): number;
2584+
25722585
/**
25732586
* Converts an object's values to an array.
25742587
* @param object Object to convert

src/namespace.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ var Type, // cyclic
2929
* @function
3030
* @param {string} name Namespace name
3131
* @param {Object.<string,*>} json JSON object
32+
* @param {number} [depth] Current nesting depth, defaults to `0`
3233
* @returns {Namespace} Created namespace
3334
* @throws {TypeError} If arguments are invalid
3435
*/
35-
Namespace.fromJSON = function fromJSON(name, json) {
36-
return new Namespace(name, json.options).addJSON(json.nested);
36+
Namespace.fromJSON = function fromJSON(name, json, depth) {
37+
depth = util.checkDepth(depth);
38+
return new Namespace(name, json.options).addJSON(json.nested, depth);
3739
};
3840

3941
/**
@@ -191,9 +193,11 @@ Namespace.prototype.toJSON = function toJSON(toJSONOptions) {
191193
/**
192194
* Adds nested objects to this namespace from nested object descriptors.
193195
* @param {Object.<string,AnyNestedObject>} nestedJson Any nested object descriptors
196+
* @param {number} [depth] Current nesting depth, defaults to `0`
194197
* @returns {Namespace} `this`
195198
*/
196-
Namespace.prototype.addJSON = function addJSON(nestedJson) {
199+
Namespace.prototype.addJSON = function addJSON(nestedJson, depth) {
200+
depth = util.checkDepth(depth);
197201
var ns = this;
198202
/* istanbul ignore else */
199203
if (nestedJson) {
@@ -208,7 +212,7 @@ Namespace.prototype.addJSON = function addJSON(nestedJson) {
208212
? Service.fromJSON
209213
: nested.id !== undefined
210214
? Field.fromJSON
211-
: Namespace.fromJSON )(names[i], nested)
215+
: Namespace.fromJSON )(names[i], nested, depth + 1)
212216
);
213217
}
214218
}

src/parse.js

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,8 @@ function parse(source, root, options) {
311311
}
312312

313313

314-
function parseCommon(parent, token) {
314+
function parseCommon(parent, token, depth) {
315+
depth = util.checkDepth(depth);
315316
switch (token) {
316317

317318
case "option":
@@ -320,7 +321,7 @@ function parse(source, root, options) {
320321
return true;
321322

322323
case "message":
323-
parseType(parent, token);
324+
parseType(parent, token, depth + 1);
324325
return true;
325326

326327
case "enum":
@@ -341,14 +342,14 @@ function parse(source, root, options) {
341342
}
342343
/* eslint-disable no-warning-comments */
343344
// TODO: actually enforce visiblity modifiers like protoc does.
344-
return parseCommon(parent, token);
345+
return parseCommon(parent, token, depth);
345346

346347
case "service":
347-
parseService(parent, token);
348+
parseService(parent, token, depth + 1);
348349
return true;
349350

350351
case "extend":
351-
parseExtension(parent, token);
352+
parseExtension(parent, token, depth);
352353
return true;
353354
}
354355
return false;
@@ -376,15 +377,16 @@ function parse(source, root, options) {
376377
}
377378
}
378379

379-
function parseType(parent, token) {
380+
function parseType(parent, token, depth) {
381+
depth = util.checkDepth(depth);
380382

381383
/* istanbul ignore if */
382384
if (!nameRe.test(token = next()))
383385
throw illegal(token, "type name");
384386

385387
var type = new Type(token);
386388
ifBlock(type, function parseType_block(token) {
387-
if (parseCommon(type, token))
389+
if (parseCommon(type, token, depth))
388390
return;
389391

390392
switch (token) {
@@ -401,22 +403,22 @@ function parse(source, root, options) {
401403
throw illegal(token);
402404
/* eslint-disable no-fallthrough */
403405
case "repeated":
404-
parseField(type, token);
406+
parseField(type, token, undefined, depth + 1);
405407
break;
406408

407409
case "optional":
408410
/* istanbul ignore if */
409411
if (edition === "proto3") {
410-
parseField(type, "proto3_optional");
412+
parseField(type, "proto3_optional", undefined, depth + 1);
411413
} else if (edition !== "proto2") {
412414
throw illegal(token);
413415
} else {
414-
parseField(type, "optional");
416+
parseField(type, "optional", undefined, depth + 1);
415417
}
416418
break;
417419

418420
case "oneof":
419-
parseOneOf(type, token);
421+
parseOneOf(type, token, depth + 1);
420422
break;
421423

422424
case "extensions":
@@ -434,7 +436,7 @@ function parse(source, root, options) {
434436
}
435437

436438
push(token);
437-
parseField(type, "optional");
439+
parseField(type, "optional", undefined, depth + 1);
438440
break;
439441
}
440442
});
@@ -444,10 +446,10 @@ function parse(source, root, options) {
444446
}
445447
}
446448

447-
function parseField(parent, rule, extend) {
449+
function parseField(parent, rule, extend, depth) {
448450
var type = next();
449451
if (type === "group") {
450-
parseGroup(parent, rule, extend);
452+
parseGroup(parent, rule, extend, depth);
451453
return;
452454
}
453455
// Type names can consume multiple tokens, in multiple variants:
@@ -504,7 +506,8 @@ function parse(source, root, options) {
504506
}
505507
}
506508

507-
function parseGroup(parent, rule, extend) {
509+
function parseGroup(parent, rule, extend, depth) {
510+
depth = util.checkDepth(depth);
508511
if (edition >= 2023) {
509512
throw illegal("group");
510513
}
@@ -535,20 +538,20 @@ function parse(source, root, options) {
535538
break;
536539
case "required":
537540
case "repeated":
538-
parseField(type, token);
541+
parseField(type, token, undefined, depth + 1);
539542
break;
540543

541544
case "optional":
542545
/* istanbul ignore if */
543546
if (edition === "proto3") {
544-
parseField(type, "proto3_optional");
547+
parseField(type, "proto3_optional", undefined, depth + 1);
545548
} else {
546-
parseField(type, "optional");
549+
parseField(type, "optional", undefined, depth + 1);
547550
}
548551
break;
549552

550553
case "message":
551-
parseType(type, token);
554+
parseType(type, token, depth + 1);
552555
break;
553556

554557
case "enum":
@@ -567,10 +570,10 @@ function parse(source, root, options) {
567570
token = next();
568571
switch (token) {
569572
case "message":
570-
parseType(type, token);
573+
parseType(type, token, depth + 1);
571574
break;
572575
case "enum":
573-
parseType(type, token);
576+
parseType(type, token, depth + 1);
574577
break;
575578
default:
576579
throw illegal(token);
@@ -629,7 +632,7 @@ function parse(source, root, options) {
629632
parent.add(field);
630633
}
631634

632-
function parseOneOf(parent, token) {
635+
function parseOneOf(parent, token, depth) {
633636

634637
/* istanbul ignore if */
635638
if (!nameRe.test(token = next()))
@@ -642,7 +645,7 @@ function parse(source, root, options) {
642645
skip(";");
643646
} else {
644647
push(token);
645-
parseField(oneof, "optional");
648+
parseField(oneof, "optional", undefined, depth);
646649
}
647650
});
648651
parent.add(oneof);
@@ -750,7 +753,8 @@ function parse(source, root, options) {
750753
setParsedOption(parent, option, optionValue, propName);
751754
}
752755

753-
function parseOptionValue(parent, name) {
756+
function parseOptionValue(parent, name, depth) {
757+
depth = util.checkDepth(depth);
754758
// { a: "foo" b { c: "bar" } }
755759
if (skip("{", true)) {
756760
var objectResult = {};
@@ -773,7 +777,7 @@ function parse(source, root, options) {
773777
// option (my_option) = {
774778
// repeated_value: [ "foo", "bar" ]
775779
// };
776-
value = parseOptionValue(parent, name + "." + token);
780+
value = parseOptionValue(parent, name + "." + token, depth + 1);
777781
} else if (peek() === "[") {
778782
value = [];
779783
var lastValue;
@@ -840,15 +844,16 @@ function parse(source, root, options) {
840844
return parent;
841845
}
842846

843-
function parseService(parent, token) {
847+
function parseService(parent, token, depth) {
848+
depth = util.checkDepth(depth);
844849

845850
/* istanbul ignore if */
846851
if (!nameRe.test(token = next()))
847852
throw illegal(token, "service name");
848853

849854
var service = new Service(token);
850855
ifBlock(service, function parseService_block(token) {
851-
if (parseCommon(service, token)) {
856+
if (parseCommon(service, token, depth)) {
852857
return;
853858
}
854859

@@ -918,7 +923,7 @@ function parse(source, root, options) {
918923
parent.add(method);
919924
}
920925

921-
function parseExtension(parent, token) {
926+
function parseExtension(parent, token, depth) {
922927

923928
/* istanbul ignore if */
924929
if (!typeRefRe.test(token = next()))
@@ -930,15 +935,15 @@ function parse(source, root, options) {
930935

931936
case "required":
932937
case "repeated":
933-
parseField(parent, token, reference);
938+
parseField(parent, token, reference, depth + 1);
934939
break;
935940

936941
case "optional":
937942
/* istanbul ignore if */
938943
if (edition === "proto3") {
939-
parseField(parent, "proto3_optional", reference);
944+
parseField(parent, "proto3_optional", reference, depth + 1);
940945
} else {
941-
parseField(parent, "optional", reference);
946+
parseField(parent, "optional", reference, depth + 1);
942947
}
943948
break;
944949

@@ -947,7 +952,7 @@ function parse(source, root, options) {
947952
if (edition === "proto2" || !typeRefRe.test(token))
948953
throw illegal(token);
949954
push(token);
950-
parseField(parent, "optional", reference);
955+
parseField(parent, "optional", reference, depth + 1);
951956
break;
952957
}
953958
});
@@ -998,7 +1003,7 @@ function parse(source, root, options) {
9981003
default:
9991004

10001005
/* istanbul ignore else */
1001-
if (parseCommon(ptr, token)) {
1006+
if (parseCommon(ptr, token, 0)) {
10021007
head = false;
10031008
continue;
10041009
}

src/root.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,16 @@ function Root(options) {
5555
* Loads a namespace descriptor into a root namespace.
5656
* @param {INamespace} json Namespace descriptor
5757
* @param {Root} [root] Root namespace, defaults to create a new one if omitted
58+
* @param {number} [depth] Current nesting depth, defaults to `0`
5859
* @returns {Root} Root namespace
5960
*/
60-
Root.fromJSON = function fromJSON(json, root) {
61+
Root.fromJSON = function fromJSON(json, root, depth) {
62+
depth = util.checkDepth(depth);
6163
if (!root)
6264
root = new Root();
6365
if (json.options)
6466
root.setOptions(json.options);
65-
return root.addJSON(json.nested).resolveAll();
67+
return root.addJSON(json.nested, depth).resolveAll();
6668
};
6769

6870
/**

0 commit comments

Comments
 (0)