This is an automated email from the ASF dual-hosted git repository.

dengliming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new e8988c65 fix(ApiDebug): render non-JSON response bodies as plain text 
(#708)
e8988c65 is described below

commit e8988c65f46bcdb94b37ce5b24b66631418c36a6
Author: Bhaskar Gurram <[email protected]>
AuthorDate: Sat Oct 10 12:53:44 2026 -0400

    fix(ApiDebug): render non-JSON response bodies as plain text (#708)
    
    The API debug panel passed non-JSON upstream response bodies to
    ReactHtmlParser, so markup returned by the gateway (styles, forms,
    images, embedded content) became live elements inside the dashboard.
    
    Render those bodies as text inside a <pre> with wrapped, preserved
    whitespace, matching ImportResultModal. React escapes the text, so
    markup is shown literally and no elements are created. JSON bodies are
    still shown with ReactJson.
    
    react-html-parser has no other users, so drop it from package.json and
    package-lock.json, and remove its mock from ApiDebug.state.test.js. Add
    a test that a markup body is rendered as literal text.
    
    Fixes #614
---
 package-lock.json                                  | 107 +--------------------
 package.json                                       |   1 -
 src/routes/Document/components/ApiDebug.js         |   6 +-
 .../Document/components/ApiDebug.state.test.js     |   1 -
 src/routes/Document/components/ApiDebug.test.js    |  31 ++++++
 5 files changed, 39 insertions(+), 107 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index daecd885..ba3a5647 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -31,7 +31,6 @@
         "react-container-query": "^0.11.0",
         "react-document-title": "^2.0.3",
         "react-dom": "^16.13.1",
-        "react-html-parser": "^2.0.2",
         "react-intl-universal": "^2.4.2",
         "react-json-view": "^1.21.3",
         "react-resizable": "^1.11.0",
@@ -21661,6 +21660,7 @@
       "version": "2.0.4",
       "resolved": 
"https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz";,
       "integrity": 
"sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true,
       "license": "ISC"
     },
     "node_modules/ini": {
@@ -36437,108 +36437,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/react-html-parser": {
-      "version": "2.0.2",
-      "resolved": 
"https://registry.npmmirror.com/react-html-parser/-/react-html-parser-2.0.2.tgz";,
-      "integrity": 
"sha512-XeerLwCVjTs3njZcgCOeDUqLgNIt/t+6Jgi5/qPsO/krUWl76kWKXMeVs2LhY2gwM6X378DkhLjur0zUQdpz0g==",
-      "license": "MIT",
-      "dependencies": {
-        "htmlparser2": "^3.9.0"
-      },
-      "peerDependencies": {
-        "react": "^0.14.0 || ^15.0.0 || ^16.0.0-0"
-      }
-    },
-    "node_modules/react-html-parser/node_modules/dom-serializer": {
-      "version": "0.2.2",
-      "resolved": 
"https://registry.npmmirror.com/dom-serializer/-/dom-serializer-0.2.2.tgz";,
-      "integrity": 
"sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
-      "license": "MIT",
-      "dependencies": {
-        "domelementtype": "^2.0.1",
-        "entities": "^2.0.0"
-      }
-    },
-    
"node_modules/react-html-parser/node_modules/dom-serializer/node_modules/domelementtype":
 {
-      "version": "2.3.0",
-      "resolved": 
"https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz";,
-      "integrity": 
"sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/fb55";
-        }
-      ],
-      "license": "BSD-2-Clause"
-    },
-    
"node_modules/react-html-parser/node_modules/dom-serializer/node_modules/entities":
 {
-      "version": "2.2.0",
-      "resolved": 
"https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz";,
-      "integrity": 
"sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
-      "license": "BSD-2-Clause",
-      "funding": {
-        "url": "https://github.com/fb55/entities?sponsor=1";
-      }
-    },
-    "node_modules/react-html-parser/node_modules/domelementtype": {
-      "version": "1.3.1",
-      "resolved": 
"https://registry.npmmirror.com/domelementtype/-/domelementtype-1.3.1.tgz";,
-      "integrity": 
"sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
-      "license": "BSD-2-Clause"
-    },
-    "node_modules/react-html-parser/node_modules/domhandler": {
-      "version": "2.4.2",
-      "resolved": 
"https://registry.npmmirror.com/domhandler/-/domhandler-2.4.2.tgz";,
-      "integrity": 
"sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
-      "license": "BSD-2-Clause",
-      "dependencies": {
-        "domelementtype": "1"
-      }
-    },
-    "node_modules/react-html-parser/node_modules/domutils": {
-      "version": "1.7.0",
-      "resolved": 
"https://registry.npmmirror.com/domutils/-/domutils-1.7.0.tgz";,
-      "integrity": 
"sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
-      "license": "BSD-2-Clause",
-      "dependencies": {
-        "dom-serializer": "0",
-        "domelementtype": "1"
-      }
-    },
-    "node_modules/react-html-parser/node_modules/entities": {
-      "version": "1.1.2",
-      "resolved": 
"https://registry.npmmirror.com/entities/-/entities-1.1.2.tgz";,
-      "integrity": 
"sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
-      "license": "BSD-2-Clause"
-    },
-    "node_modules/react-html-parser/node_modules/htmlparser2": {
-      "version": "3.10.1",
-      "resolved": 
"https://registry.npmmirror.com/htmlparser2/-/htmlparser2-3.10.1.tgz";,
-      "integrity": 
"sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
-      "license": "MIT",
-      "dependencies": {
-        "domelementtype": "^1.3.1",
-        "domhandler": "^2.3.0",
-        "domutils": "^1.5.1",
-        "entities": "^1.1.1",
-        "inherits": "^2.0.1",
-        "readable-stream": "^3.1.1"
-      }
-    },
-    "node_modules/react-html-parser/node_modules/readable-stream": {
-      "version": "3.6.2",
-      "resolved": 
"https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz";,
-      "integrity": 
"sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
-      "license": "MIT",
-      "dependencies": {
-        "inherits": "^2.0.3",
-        "string_decoder": "^1.1.1",
-        "util-deprecate": "^1.0.1"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
     "node_modules/react-intl-universal": {
       "version": "2.13.4",
       "resolved": 
"https://registry.npmmirror.com/react-intl-universal/-/react-intl-universal-2.13.4.tgz";,
@@ -38213,6 +38111,7 @@
       "version": "5.1.2",
       "resolved": 
"https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz";,
       "integrity": 
"sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/safe-push-apply": {
@@ -39648,6 +39547,7 @@
       "version": "1.1.1",
       "resolved": 
"https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz";,
       "integrity": 
"sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "safe-buffer": "~5.1.0"
@@ -44533,6 +44433,7 @@
       "version": "1.0.2",
       "resolved": 
"https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz";,
       "integrity": 
"sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/util.promisify": {
diff --git a/package.json b/package.json
index 1b10fbd2..fb53077c 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,6 @@
     "react-container-query": "^0.11.0",
     "react-document-title": "^2.0.3",
     "react-dom": "^16.13.1",
-    "react-html-parser": "^2.0.2",
     "react-intl-universal": "^2.4.2",
     "react-json-view": "^1.21.3",
     "react-resizable": "^1.11.0",
diff --git a/src/routes/Document/components/ApiDebug.js 
b/src/routes/Document/components/ApiDebug.js
index 3098c512..e33ea306 100644
--- a/src/routes/Document/components/ApiDebug.js
+++ b/src/routes/Document/components/ApiDebug.js
@@ -37,7 +37,6 @@ import React, {
   useState,
 } from "react";
 import ReactJson from "react-json-view";
-import ReactHtmlParser from "react-html-parser";
 import fetch from "dva/fetch";
 import {
   createOrUpdateMockRequest,
@@ -501,7 +500,10 @@ function ApiDebug() {
           Object.keys(responseInfo).length ? (
             <ReactJson src={responseInfo.body} name={false} />
           ) : responseInfo.body ? (
-            ReactHtmlParser(responseInfo.body)
+            // Non-JSON upstream bodies are untrusted: show them as text only.
+            <pre style={{ whiteSpace: "pre-wrap", wordWrap: "break-word" }}>
+              {responseInfo.body}
+            </pre>
           ) : (
             <Empty description={false} />
           )}
diff --git a/src/routes/Document/components/ApiDebug.state.test.js 
b/src/routes/Document/components/ApiDebug.state.test.js
index 31422397..cb89d475 100644
--- a/src/routes/Document/components/ApiDebug.state.test.js
+++ b/src/routes/Document/components/ApiDebug.state.test.js
@@ -41,7 +41,6 @@ jest.mock("antd", () => {
 });
 
 jest.mock("dva/fetch", () => jest.fn());
-jest.mock("react-html-parser", () => jest.fn(() => null));
 jest.mock("react-json-view", () => () => null);
 jest.mock("./HeadersEditor", () => {
   const ReactModule = jest.requireActual("react");
diff --git a/src/routes/Document/components/ApiDebug.test.js 
b/src/routes/Document/components/ApiDebug.test.js
index dec22674..f8809636 100644
--- a/src/routes/Document/components/ApiDebug.test.js
+++ b/src/routes/Document/components/ApiDebug.test.js
@@ -131,3 +131,34 @@ it("validates HTTP(S) URLs with or without an explicit 
port", () => {
   expect(isValidHttpUrl("http://api.example.test:8080/orders";)).toBe(true);
   expect(isValidHttpUrl("ftp://api.example.test/orders";)).toBe(false);
 });
+
+it("renders a non-JSON response body as literal text instead of HTML", async 
() => {
+  const body =
+    '<img id="injected" src="x"><b>upstream</b>\n  <form id="fake"></form>';
+  fetch.mockResolvedValue({
+    text: async () => body,
+    headers: { get: () => null },
+  });
+  jest.spyOn(console, "error").mockImplementation(() => {});
+
+  const requestUrl = "https://api.example.test/orders";;
+  const { container } = render(
+    <ApiContext.Provider
+      value={{
+        ...contextValue,
+        apiDetail: { ...contextValue.apiDetail, apiPath: requestUrl },
+      }}
+    >
+      <ApiDebug />
+    </ApiContext.Provider>,
+  );
+
+  fireEvent.submit(container.querySelector("form"));
+
+  await waitFor(() => expect(container.querySelector("pre")).not.toBeNull());
+  const pre = container.querySelector("pre");
+  expect(pre.textContent).toBe(body);
+  expect(pre.children).toHaveLength(0);
+  expect(container.querySelector("#injected")).toBeNull();
+  expect(container.querySelector("#fake")).toBeNull();
+});

Reply via email to