Skip to content

Commit 5de47a0

Browse files
fix: Network map: escape double-quotes and backslashes in device attributes (#30746)
Co-authored-by: Koen Kanters <[email protected]>
1 parent 14bffd5 commit 5de47a0

File tree

2 files changed

+25
-12
lines changed

2 files changed

+25
-12
lines changed

lib/extension/networkMap.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ export default class NetworkMap extends Extension {
9292
}
9393

9494
labels.push(lastSeen);
95+
// Escape backslashes and double-quotes to avoid breaking Graphviz .dot files.
96+
const escapedLabels = labels.map((label) => label.replace(/\\/g, "\\\\").replace(/"/g, '\\"'));
9597

9698
// Shape the record according to device type
9799
if (node.type === "Coordinator") {
@@ -103,7 +105,7 @@ export default class NetworkMap extends Extension {
103105
}
104106

105107
// Add the device with its labels to the graph as a node.
106-
text += ` "${node.ieeeAddr}" [${style}, label="{${labels.join("|")}}"];\n`;
108+
text += ` "${node.ieeeAddr}" [${style}, label="{${escapedLabels.join("|")}}"];\n`;
107109

108110
/**
109111
* Add an edge between the device and its child to the graph

test/extensions/networkMap.test.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -367,23 +367,34 @@ describe("Extension: NetworkMap", () => {
367367

368368
it("Output graphviz networkmap", async () => {
369369
mock();
370-
const device = devices.bulb_color;
371-
device.lastSeen = undefined;
372-
const endpoint = device.getEndpoint(1);
373-
const data = {modelID: "test"};
374-
const payload = {data, cluster: "genOnOff", device, endpoint, type: "readResponse", linkquality: 10};
375-
await mockZHEvents.message(payload);
376-
mockMQTTEvents.message("zigbee2mqtt/bridge/request/networkmap", stringify({type: "graphviz", routes: true}));
377-
await flushPromises();
378-
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
379-
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual("zigbee2mqtt/bridge/response/networkmap");
370+
const z2mDevice = controller.zigbee.resolveEntity(devices.bulb_color.ieeeAddr);
371+
const originalDef = z2mDevice.definition;
372+
z2mDevice.definition = {
373+
...originalDef,
374+
description: `${originalDef.description} 6"`,
375+
};
376+
377+
try {
378+
const device = devices.bulb_color;
379+
device.lastSeen = undefined;
380+
const endpoint = device.getEndpoint(1);
381+
const data = {modelID: "test"};
382+
const payload = {data, cluster: "genOnOff", device, endpoint, type: "readResponse", linkquality: 10};
383+
await mockZHEvents.message(payload);
384+
mockMQTTEvents.message("zigbee2mqtt/bridge/request/networkmap", stringify({type: "graphviz", routes: true}));
385+
await flushPromises();
386+
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
387+
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual("zigbee2mqtt/bridge/response/networkmap");
388+
} finally {
389+
z2mDevice.definition = originalDef;
390+
}
380391

381392
const expected = `digraph G {
382393
node[shape=record];
383394
"0x00124b00120144ae" [style="bold, filled", fillcolor="#e04e5d", fontcolor="#ffffff", label="{Coordinator|0x00124b00120144ae (0x0000)|0 seconds ago}"];
384395
"0x000b57fffec6a5b2" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{bulb|0x000b57fffec6a5b2 (0x9db1)|IKEA TRADFRI bulb E26/E27, white spectrum, globe, opal, 980 lm (LED1545G12)|9 seconds ago}"];
385396
"0x000b57fffec6a5b2" -> "0x00124b00120144ae" [penwidth=2, weight=1, color="#009900", label="92 (routes: 0x198c)"]
386-
"0x000b57fffec6a5b3" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{bulb_color|0x000b57fffec6a5b3 (0x9dcf)|Philips Hue Go (7146060PH)|unknown}"];
397+
"0x000b57fffec6a5b3" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{bulb_color|0x000b57fffec6a5b3 (0x9dcf)|Philips Hue Go 6\\" (7146060PH)|unknown}"];
387398
"0x000b57fffec6a5b3" -> "0x00124b00120144ae" [penwidth=0.5, weight=0, color="#994444", label="120"]
388399
"0x000b57fffec6a5b3" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="110"]
389400
"0x0017880104e45521" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{button_double_key|0x0017880104e45521 (0x198a)|Aqara Wireless remote switch (double rocker), 2016 model (WXKG02LM_rev1)|9 seconds ago}"];

0 commit comments

Comments
 (0)