Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve xml sources #389

Merged
merged 10 commits into from
Dec 23, 2024
2 changes: 1 addition & 1 deletion library/agent/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type Context = {
source: string;
route: string | undefined;
graphql?: string[];
xml?: unknown;
xml?: unknown[];
subdomains?: string[]; // https://expressjs.com/en/5x/api.html#req.subdomains
cache?: Map<Source, ReturnType<typeof extractStringsFromUserInput>>;
/**
Expand Down
72 changes: 67 additions & 5 deletions library/sources/FastXmlParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,84 @@ t.test("it works", async () => {
const result = parser.parse(xmlString);
t.same(result, { root: "Hello xml2js!" });

const context = {
const contextWithBody = {
remoteAddress: "::1",
method: "POST",
url: "http://localhost:4000",
query: {},
headers: {},
headers: { "content-type": "application/xml" },
body: xmlString,
cookies: {},
routeParams: {},
source: "express",
route: "/posts/:id",
route: "/",
};

runWithContext(contextWithBody, () => {
const result = parser.parse(xmlString);
t.same(result, { root: "Hello xml2js!" });
t.same(getContext()?.xml, [{ root: "Hello xml2js!" }]);
});

const xmlString2 =
'<root><list><person name="John"><age>35</age></person></list></root>';

const contextWithQuery = {
remoteAddress: "::1",
method: "GET",
headers: {},
url: "http://localhost:4000",
query: {
xml: xmlString2,
},
body: undefined,
cookies: {},
routeParams: {},
source: "express",
route: "/",
};

runWithContext(context, () => {
// Ignores if xml string not in the context
runWithContext(contextWithQuery, () => {
const result = parser.parse(xmlString);
t.same(result, { root: "Hello xml2js!" });
t.same(getContext()?.xml, { root: "Hello xml2js!" });
t.same(getContext()?.xml, undefined);
});

runWithContext(contextWithQuery, () => {
const parser2 = new XMLParser({
ignoreAttributes: false,
});

const result = parser2.parse(xmlString2);

const expected = {
root: {
list: {
person: {
age: 35,
"@_name": "John",
},
},
},
};

t.same(result, expected);
t.same(getContext()?.xml, [expected]);

// Adds additional xml to the context xml array
parser2.parse(xmlString2);

t.same(getContext()?.xml, [expected, expected]);
});

// XML is not in the context
runWithContext({ ...contextWithQuery, query: {}, xml: undefined }, () => {
const res = parser.parse(xmlString);
t.same(res, { root: "Hello xml2js!" });
t.same(getContext()?.xml, undefined);
});

// Zen ignores non string values
t.same(parser.parse(123), {});
});
12 changes: 7 additions & 5 deletions library/sources/FastXmlParser.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable prefer-rest-params */
import { getContext, updateContext } from "../agent/Context";
import { getContext } from "../agent/Context";
import { Hooks } from "../agent/hooks/Hooks";
import { wrapExport } from "../agent/hooks/wrapExport";
import { wrapNewInstance } from "../agent/hooks/wrapNewInstance";
import { Wrapper } from "../agent/Wrapper";
import { isPlainObject } from "../helpers/isPlainObject";
import { addXmlToContext } from "./xml/addXmlToContext";
import { isXmlInContext } from "./xml/isXmlInContext";

/**
* Wrapper for fast-xml-parser package.
Expand All @@ -25,14 +27,14 @@ export class FastXmlParser implements Wrapper {

const xmlString = args[0] as string;

if (typeof context.body !== "string" || context.body !== xmlString) {
// The XML string is not in the body, so currently we don't check it
// Check if the XML string is in the request context
if (!isXmlInContext(xmlString, context)) {
return args;
}

// Replace the body in the context with the parsed result
// Add the parsed XML to the context
if (result && isPlainObject(result)) {
updateContext(context, "xml", result);
addXmlToContext(result, context);
timokoessler marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
53 changes: 38 additions & 15 deletions library/sources/Xml2js.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,52 @@ t.test("it works", async () => {
t.same(getContext()?.xml, undefined);
});

const context: Context = {
remoteAddress: "::1",
method: "POST",
url: "http://localhost:4000",
query: {},
headers: {},
body: xmlString,
cookies: {},
routeParams: {},
source: "express",
route: "/posts/:id",
const getTestContext = (): Context => {
return {
remoteAddress: "::1",
method: "POST",
url: "http://localhost:4000",
query: {},
headers: {},
body: xmlString,
cookies: {},
routeParams: {},
source: "express",
route: "/posts/:id",
};
};

await runWithContext(context, async () => {
await runWithContext(getTestContext(), async () => {
const result = await parseStringPromise(xmlString);
t.same(result, { root: "Hello xml2js!" });
t.same(getContext()?.xml, { root: "Hello xml2js!" });
t.same(getContext()?.xml, [{ root: "Hello xml2js!" }]);
});

runWithContext(context, () => {
const sharedContext = getTestContext();

runWithContext(sharedContext, () => {
parseString(xmlString, (err, result) => {
t.same(result, { root: "Hello xml2js!" });
t.same(getContext()?.xml, { root: "Hello xml2js!" });
t.same(getContext()?.xml, [{ root: "Hello xml2js!" }]);
});
});

// Adds addition xml to the context xml array
runWithContext(sharedContext, () => {
parseString(xmlString, (err, result) => {
t.same(result, { root: "Hello xml2js!" });
t.same(getContext()?.xml, [
{ root: "Hello xml2js!" },
{ root: "Hello xml2js!" },
]);
});
});

// Ignore xml not in the context
runWithContext(getTestContext(), () => {
parseString("<test><ele>ABC</ele></test>", (err, result) => {
t.same(result, { test: { ele: ["ABC"] } });
t.same(getContext()?.xml, undefined);
});
});
});
10 changes: 6 additions & 4 deletions library/sources/Xml2js.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-disable prefer-rest-params */
import { getContext, updateContext, runWithContext } from "../agent/Context";
import { getContext, runWithContext } from "../agent/Context";
import { Hooks } from "../agent/hooks/Hooks";
import { wrapExport } from "../agent/hooks/wrapExport";
import { Wrapper } from "../agent/Wrapper";
import { isPlainObject } from "../helpers/isPlainObject";
import { addXmlToContext } from "./xml/addXmlToContext";
import { isXmlInContext } from "./xml/isXmlInContext";

/**
* Wrapper for xml2js package.
Expand All @@ -28,16 +30,16 @@ export class Xml2js implements Wrapper {

const xmlString = args[0] as string;

if (typeof context.body !== "string" || context.body !== xmlString) {
// The XML string is not in the body, so currently we don't check it
// Check if the XML string is in the request context
if (!isXmlInContext(xmlString, context)) {
return args;
}

// Wrap the callback to get the parsed result
const originalCallback = args[1] as Function;
args[1] = function wrapCallback(err: Error, result: unknown) {
if (result && isPlainObject(result)) {
updateContext(context, "xml", result);
addXmlToContext(result, context);
}

runWithContext(context, () => originalCallback(err, result));
Expand Down
26 changes: 7 additions & 19 deletions library/sources/XmlMinusJs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import { XmlMinusJs } from "./XmlMinusJs";
import { readFile } from "fs/promises";
import { createTestAgent } from "../helpers/createTestAgent";

t.test("xml2js works", async () => {
const agent = createTestAgent();

agent.start([new XmlMinusJs()]);
const agent = createTestAgent();
agent.start([new XmlMinusJs()]);

const xmljs = require("xml-js");
const xmljs = require("xml-js");

t.test("xml2js works", async () => {
const xmlString = (
await readFile(join(__dirname, "fixtures", "products.xml"), "utf8")
).toString();
Expand Down Expand Up @@ -40,17 +39,11 @@ t.test("xml2js works", async () => {
runWithContext(context, () => {
const result = xmljs.xml2js(xmlString, { compact: true });
t.same(result, expectedCompact);
t.same(getContext()?.xml, expectedCompact);
t.same(getContext()?.xml, [expectedCompact]);
});
});

t.test("xml2json works", async () => {
const agent = createTestAgent();

agent.start([new XmlMinusJs()]);

const xmljs = require("xml-js");

const xmlString = "<root>Hello xml-js!</root>";

const result = xmljs.xml2json(xmlString);
Expand All @@ -75,16 +68,11 @@ t.test("xml2json works", async () => {
runWithContext(context, () => {
const result = xmljs.xml2json(xmlString, { compact: true });
t.same(result, '{"root":{"_text":"Hello xml-js!"}}');
t.same(getContext()?.xml, { root: { _text: "Hello xml-js!" } });
t.same(getContext()?.xml, [{ root: { _text: "Hello xml-js!" } }]);
});
});

t.test("Ignore if xml is not in the body", async () => {
const agent = createTestAgent();
agent.start([new XmlMinusJs()]);

const xmljs = require("xml-js");

t.test("Ignore if xml is not in the context", async () => {
const xmlString = "<root>Hello xml-js!</root>";

const context = {
Expand Down
11 changes: 6 additions & 5 deletions library/sources/XmlMinusJs.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-disable prefer-rest-params */
import { getContext, updateContext } from "../agent/Context";
import { getContext } from "../agent/Context";
import { Hooks } from "../agent/hooks/Hooks";
import { wrapExport } from "../agent/hooks/wrapExport";
import { Wrapper } from "../agent/Wrapper";
import { isPlainObject } from "../helpers/isPlainObject";
import { addXmlToContext } from "./xml/addXmlToContext";
import { isXmlInContext } from "./xml/isXmlInContext";

/**
* Wrapper for xml-js package.
Expand All @@ -22,17 +24,16 @@ export class XmlMinusJs implements Wrapper {

const xmlString = args[0] as string;

if (typeof context.body !== "string" || context.body !== xmlString) {
// We only want to set the parsed XML result as context.xml
// When xml2js(req.body) or xml2json(req.body) is called
// Check if the XML string is in the request context
if (!isXmlInContext(xmlString, context)) {
return args;
}

const parsed = jsonStr ? JSON.parse(result as string) : result;

// Replace the body in the context with the parsed result
if (parsed && isPlainObject(parsed)) {
updateContext(context, "xml", parsed);
addXmlToContext(parsed, context);
}
}

Expand Down
12 changes: 12 additions & 0 deletions library/sources/xml/addXmlToContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Context, updateContext } from "../../agent/Context";

/**
* Adds the XML to the context XML array if it exists, or creates a new array if it doesn't.
*/
export function addXmlToContext(xml: any, context: Context) {
if (Array.isArray(context.xml)) {
updateContext(context, "xml", context.xml.concat(xml));
} else {
updateContext(context, "xml", [xml]);
}
}
27 changes: 27 additions & 0 deletions library/sources/xml/isXmlInContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Context } from "../../agent/Context";
import { SOURCES } from "../../agent/Source";
import { extractStringsFromUserInputCached } from "../../helpers/extractStringsFromUserInputCached";

/**
* Checks if the XML string can be found in the context.
*/
export function isXmlInContext(xml: string, context: Context): boolean {
for (const source of SOURCES) {
if (source === "xml") {
// Skip parsed XML
continue;
}
const userInput = extractStringsFromUserInputCached(context, source);
if (!userInput) {
continue;
}

for (const str of userInput) {
if (str === xml) {
return true;
}
}
}

return false;
}
Loading