Skip to content

Commit

Permalink
fix: #186 avoid infinite recursion in filter method (#187)
Browse files Browse the repository at this point in the history
* reproduce issue with test

* reproduce issue with test

* prettied and cleaned

* improved code

* remove empty line

* code and test improvements

* formatting
  • Loading branch information
miquelbeltran authored May 6, 2024
1 parent 01a4f4c commit 8103e92
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 5 deletions.
29 changes: 24 additions & 5 deletions lib/raygun.messageBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,37 @@ type UserMessageData = RawUserData | string | undefined;
const humanString = require("object-to-human-string");
const packageDetails = require("../package.json");

function filterKeys(obj: object, filters: string[]): object {
/**
* Filter properties in obj according to provided filters.
* Also removes any recursive self-referencing object.
* @param obj object to apply filter
* @param filters list of keys to filter
* @param explored Set that contains already explored nodes, used internally
*/
function filterKeys(
obj: object,
filters: string[],
explored: Set<object> | null = null,
): object {
if (!obj || !filters || typeof obj !== "object") {
return obj;
}

// create or update the explored set with the incoming object
const _explored = explored?.add(obj) || new Set([obj]);

// Make temporary copy of the object to avoid mutating the original
// Cast to Record<string, object> to enforce type check and avoid using any
const _obj = { ...obj } as Record<string, object>;
Object.keys(obj).forEach(function (i) {
if (filters.indexOf(i) > -1) {
delete _obj[i];

Object.keys(obj).forEach(function (key) {
// Remove child if:
// - the key is in the filter array
// - the value is already in the explored Set
if (filters.indexOf(key) > -1 || _explored.has(_obj[key])) {
delete _obj[key];
} else {
_obj[i] = filterKeys(_obj[i], filters);
_obj[key] = filterKeys(_obj[key], filters, _explored);
}
});
return _obj;
Expand Down
47 changes: 47 additions & 0 deletions test/raygun.messageBuilder_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,10 @@ test("filter keys tests", function (t) {
});
var message = builder.build();

// Original object should not be modified
t.equal(body.username, "[email protected]");
t.equal(body.password, "nice try");

t.test("form is filtered", function (tt) {
tt.equal(message.details.request.form.username, undefined);
tt.equal(message.details.request.form.password, undefined);
Expand All @@ -399,6 +403,49 @@ test("filter keys tests", function (t) {
});
});

test("avoid infinite recursion in filter method", function (t) {
let builder = new MessageBuilder({
filters: ["filtered"],
});

// body self-references, causing a potential infinite recursion in the filter method
// Causes exception: Maximum call stack size exceeded
let body = {
key: "value",
filtered: true,
};
// second level self-reference
let other = {
body: body,
filtered: true,
};
body.myself = body;
body.other = other;

let queryString = {};
let headers = {};

// performs filter on set
builder.setRequestDetails({
body: body,
query: queryString,
headers: headers,
});
var message = builder.build();

// key is preserved
t.equal(message.details.request.form.key, "value");
// property in "filters" is filtered
t.equal(message.details.request.form.filtered, undefined);
t.equal(message.details.request.form.other.filtered, undefined);
// self-referencing objects are also not included
t.equal(message.details.request.form.myself, undefined);
t.equal(message.details.request.form.other.body, undefined);

// test should finish
t.end();
});

test("custom tags", function (t) {
t.test("with array", function (tt) {
var builder = new MessageBuilder();
Expand Down

0 comments on commit 8103e92

Please sign in to comment.