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

bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 5b0d86cc0a xdebug probe-full-json: encoding the origin body (#12463)
5b0d86cc0a is described below

commit 5b0d86cc0a4105766876cbc39242e265085c994e
Author: Brian Neradt <[email protected]>
AuthorDate: Tue Sep 2 14:28:53 2025 -0500

    xdebug probe-full-json: encoding the origin body (#12463)
    
    For aything beyond very simple text bodies, JSON parsers for
    probe-full-json were breaking upon the content of the body (such as html
    bodies, for example). This patch adds escaping and hex encoding of body
    content to preserve the JSON format. It also adds a way to not include
    the body if the user so desires.
---
 doc/admin-guide/plugins/xdebug.en.rst              |  27 ++-
 plugins/xdebug/CMakeLists.txt                      |   9 +-
 plugins/xdebug/unit_tests/test_xdebug_utils.cc     | 195 +++++++++++++++++++++
 plugins/xdebug/xdebug.cc                           |  72 +++++++-
 plugins/xdebug/xdebug_transforms.cc                | 141 ++++++++++++++-
 plugins/xdebug/xdebug_types.h                      |  14 ++
 plugins/xdebug/xdebug_utils.cc                     | 108 ++++++++++++
 plugins/xdebug/xdebug_utils.h                      |  57 ++++++
 .../xdebug/x_probe_full_json/gold/jq.gold          |   3 -
 .../xdebug/x_probe_full_json/gold/jq_escaped.gold  |   3 +
 .../xdebug/x_probe_full_json/gold/jq_hex.gold      |   3 +
 .../xdebug/x_probe_full_json/gold/jq_nobody.gold   |   3 +
 .../x_probe_full_json.replay.yaml                  |  80 ++++++++-
 .../x_probe_full_json/x_probe_full_json.test.py    |  48 ++++-
 14 files changed, 740 insertions(+), 23 deletions(-)

diff --git a/doc/admin-guide/plugins/xdebug.en.rst 
b/doc/admin-guide/plugins/xdebug.en.rst
index 8946f03959..177f35058f 100644
--- a/doc/admin-guide/plugins/xdebug.en.rst
+++ b/doc/admin-guide/plugins/xdebug.en.rst
@@ -83,7 +83,11 @@ Diags
 
 Probe
     All request and response headers are written to the response body. Because
-    the body is altered, it disables writing to cache.
+    the body is altered, it disables writing to cache. Further, the 
``Content-Type``
+    value is modified to ``application/json`` and an 
``X-Original-Content-Type``
+    response header is added to indicate the original response ``Content-Type``
+    value.
+
     In conjunction with the `fwd` tag, the response body will contain a
     chronological log of all headers for all transactions used for this
     response.
@@ -104,7 +108,9 @@ Probe-Full-JSON
     structured JSON format. In contrast to Probe, the response content with
     this feature is parsable with JSON parsing tools like ``jq``. Because the
     body is altered, it disables writing to cache and changes the Content-Type
-    to ``application/json``.
+    to ``application/json``.  However, as with the ``probe`` header, a
+    ``X-Original-Content-Type`` response header is added to indicate the 
original
+    response ``Content-Type`` value.
 
     JSON Nodes:
 
@@ -114,7 +120,22 @@ Probe-Full-JSON
     - ``server-response``: Headers from the origin server to the proxy.
     - ``proxy-response``: Headers from the proxy to the client.
 
-    Here's an example of the JSON output from the `x_probe_full_json` test::
+    For the ``server-body`` value, by default the plugin chooses an encoding
+    based on the original response ``Content-Type``:
+
+      - Textual content types (e.g. ``text/*``, and types containing ``json``,
+        ``xml``, ``html``, ``csv``, or ``javascript``) are JSON-escaped.
+      - Other content types are hex-encoded.
+
+    You can override the derived encoding by providing an option with the
+    ``probe-full-json`` header value:
+
+      - ``X-Debug: probe-full-json=escape`` forces JSON escaping of the origin 
body.
+      - ``X-Debug: probe-full-json=hex`` forces hex encoding of the origin 
body.
+      - ``X-Debug: probe-full-json=nobody`` omits the origin body entirely.
+
+
+    Here's an example of the JSON output::
 
         $ curl -s -H"uuid: 1" -H "Host: example.com" -H "X-Debug: 
probe-full-json" http://127.0.0.1:61003/test | jq
         {
diff --git a/plugins/xdebug/CMakeLists.txt b/plugins/xdebug/CMakeLists.txt
index 506617617c..61632a60ec 100644
--- a/plugins/xdebug/CMakeLists.txt
+++ b/plugins/xdebug/CMakeLists.txt
@@ -15,6 +15,13 @@
 #
 #######################
 
-add_atsplugin(xdebug xdebug.cc xdebug_headers.cc xdebug_transforms.cc)
+add_atsplugin(xdebug xdebug.cc xdebug_headers.cc xdebug_transforms.cc 
xdebug_utils.cc)
 target_link_libraries(xdebug PRIVATE libswoc::libswoc)
 verify_global_plugin(xdebug)
+
+if(BUILD_TESTING)
+  add_executable(test_xdebug unit_tests/test_xdebug_utils.cc xdebug_utils.cc)
+  target_include_directories(test_xdebug PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
+  target_link_libraries(test_xdebug PRIVATE Catch2::Catch2WithMain 
libswoc::libswoc)
+  add_catch2_test(NAME test_xdebug COMMAND test_xdebug)
+endif()
diff --git a/plugins/xdebug/unit_tests/test_xdebug_utils.cc 
b/plugins/xdebug/unit_tests/test_xdebug_utils.cc
new file mode 100644
index 0000000000..182ba2a6f2
--- /dev/null
+++ b/plugins/xdebug/unit_tests/test_xdebug_utils.cc
@@ -0,0 +1,195 @@
+/** @file
+ *
+ * Unit tests for XDebug plugin utility functions.
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#include "xdebug_utils.h"
+#include "xdebug_types.h"
+#include <catch2/catch_test_macros.hpp>
+
+TEST_CASE("xdebug::parse_probe_full_json_field_value basic functionality", 
"[xdebug][utils]")
+{
+  xdebug::BodyEncoding_t encoding;
+
+  SECTION("Basic probe-full-json without suffix")
+  {
+    REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::AUTO);
+  }
+
+  SECTION("Case insensitive matching")
+  {
+    REQUIRE(xdebug::parse_probe_full_json_field_value("PROBE-FULL-JSON", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::AUTO);
+
+    REQUIRE(xdebug::parse_probe_full_json_field_value("Probe-Full-Json", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::AUTO);
+  }
+
+  SECTION("With whitespace around value")
+  {
+    REQUIRE(xdebug::parse_probe_full_json_field_value("  probe-full-json  ", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::AUTO);
+
+    REQUIRE(xdebug::parse_probe_full_json_field_value("\t\nprobe-full-json\r\n 
", encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::AUTO);
+  }
+}
+
+TEST_CASE("xdebug::parse_probe_full_json_field_value with valid suffixes", 
"[xdebug][utils]")
+{
+  xdebug::BodyEncoding_t encoding;
+
+  SECTION("hex suffix")
+  {
+    REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=hex", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::HEX);
+  }
+
+  SECTION("escape suffix")
+  {
+    
REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=escape", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::ESCAPE);
+  }
+
+  SECTION("nobody suffix")
+  {
+    
REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=nobody", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::OMIT_BODY);
+  }
+
+  SECTION("Suffixes with whitespace")
+  {
+    REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json = hex", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::HEX);
+
+    REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json= escape 
", encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::ESCAPE);
+
+    REQUIRE(xdebug::parse_probe_full_json_field_value("  probe-full-json  =  
nobody  ", encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::OMIT_BODY);
+  }
+
+  SECTION("Case insensitive suffixes")
+  {
+    REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=HEX", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::HEX);
+
+    
REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=ESCAPE", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::ESCAPE);
+
+    
REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=Nobody", 
encoding));
+    REQUIRE(encoding == xdebug::BodyEncoding_t::OMIT_BODY);
+  }
+}
+
+TEST_CASE("xdebug::parse_probe_full_json_field_value invalid cases", 
"[xdebug][utils]")
+{
+  xdebug::BodyEncoding_t encoding;
+
+  SECTION("Not probe-full-json")
+  {
+    REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe", 
encoding));
+    REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("full-json", 
encoding));
+    REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-json", 
encoding));
+    REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("x-cache", 
encoding));
+    REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("", encoding));
+  }
+
+  SECTION("Invalid suffixes")
+  {
+    
REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json=invalid",
 encoding));
+    
REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json=base64",
 encoding));
+    
REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json=json", 
encoding));
+    
REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json=none", 
encoding));
+  }
+
+  SECTION("Malformed syntax")
+  {
+    
REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json=", 
encoding));
+    
REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json==hex", 
encoding));
+    REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json 
hex", encoding)); // Missing =
+    
REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json+hex", 
encoding)); // Wrong separator
+  }
+
+  SECTION("Partial matches")
+  {
+    REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full", 
encoding));
+    REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-js", 
encoding));
+    REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("robe-full-json", 
encoding));
+  }
+}
+
+TEST_CASE("xdebug::is_textual_content_type functionality", "[xdebug][utils]")
+{
+  SECTION("Text content types")
+  {
+    REQUIRE(xdebug::is_textual_content_type("text/html"));
+    REQUIRE(xdebug::is_textual_content_type("text/plain"));
+    REQUIRE(xdebug::is_textual_content_type("text/css"));
+    REQUIRE(xdebug::is_textual_content_type("text/javascript"));
+    REQUIRE(xdebug::is_textual_content_type("text/xml"));
+  }
+
+  SECTION("JSON content types")
+  {
+    REQUIRE(xdebug::is_textual_content_type("application/json"));
+    REQUIRE(xdebug::is_textual_content_type("application/ld+json"));
+    REQUIRE(xdebug::is_textual_content_type("application/vnd.api+json"));
+  }
+
+  SECTION("XML content types")
+  {
+    REQUIRE(xdebug::is_textual_content_type("application/xml"));
+    REQUIRE(xdebug::is_textual_content_type("application/rss+xml"));
+    REQUIRE(xdebug::is_textual_content_type("application/atom+xml"));
+  }
+
+  SECTION("Other textual types")
+  {
+    REQUIRE(xdebug::is_textual_content_type("application/javascript"));
+    REQUIRE(xdebug::is_textual_content_type("text/csv"));
+    REQUIRE(xdebug::is_textual_content_type("text/html; charset=utf-8"));
+  }
+
+  SECTION("Non-textual content types")
+  {
+    REQUIRE_FALSE(xdebug::is_textual_content_type("application/octet-stream"));
+    REQUIRE_FALSE(xdebug::is_textual_content_type("image/jpeg"));
+    REQUIRE_FALSE(xdebug::is_textual_content_type("video/mp4"));
+    REQUIRE_FALSE(xdebug::is_textual_content_type("audio/mpeg"));
+    REQUIRE_FALSE(xdebug::is_textual_content_type("application/pdf"));
+    REQUIRE_FALSE(xdebug::is_textual_content_type("application/zip"));
+  }
+
+  SECTION("Case insensitive matching")
+  {
+    REQUIRE(xdebug::is_textual_content_type("TEXT/HTML"));
+    REQUIRE(xdebug::is_textual_content_type("Application/JSON"));
+    REQUIRE(xdebug::is_textual_content_type("Application/XML"));
+  }
+
+  SECTION("Edge cases")
+  {
+    REQUIRE_FALSE(xdebug::is_textual_content_type(""));
+    REQUIRE_FALSE(xdebug::is_textual_content_type("invalid"));
+    REQUIRE(xdebug::is_textual_content_type("contains-json-somewhere"));
+    REQUIRE(xdebug::is_textual_content_type("has-xml-in-name"));
+  }
+}
diff --git a/plugins/xdebug/xdebug.cc b/plugins/xdebug/xdebug.cc
index ea97c6b805..7983c29050 100644
--- a/plugins/xdebug/xdebug.cc
+++ b/plugins/xdebug/xdebug.cc
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 
+#include <cctype>
 #include <climits>
 #include <cstdlib>
 #include <cstdio>
@@ -39,6 +40,7 @@
 #include "xdebug_types.h"
 #include "xdebug_headers.h"
 #include "xdebug_transforms.h"
+#include "xdebug_utils.h"
 
 namespace xdebug
 {
@@ -579,8 +581,9 @@ XInjectResponseHeaders(TSCont /* contp */, TSEvent event, 
void *edata)
   }
 
   if (xheaders & XHEADER_X_PROBE_HEADERS || xheaders & 
XHEADER_X_PROBE_FULL_JSON) {
-    InjectOriginalContentTypeHeader(buffer, hdr, xheaders & 
XHEADER_X_PROBE_FULL_JSON);
     BodyBuilder *data = AuxDataMgr::data(txn).body_builder.get();
+
+    InjectOriginalContentTypeHeader(buffer, hdr, xheaders & 
XHEADER_X_PROBE_FULL_JSON);
     Dbg(dbg_ctl_xform, "XInjectResponseHeaders(): client resp header ready 
(probe-full-json: %d)",
         xheaders & XHEADER_X_PROBE_FULL_JSON);
     if (data == nullptr) {
@@ -647,6 +650,51 @@ isFwdFieldValue(std::string_view value, intmax_t &fwdCnt)
   return true;
 }
 
+/** Resolve encoding based on response Content-Type before body 
transformation. */
+static int
+XResolveEncoding(TSCont /* contp */, TSEvent event, void *edata)
+{
+  TSHttpTxn txn = static_cast<TSHttpTxn>(edata);
+  TSMLoc    hdr = TS_NULL_MLOC, ct_field = TS_NULL_MLOC;
+  TSMBuffer buffer;
+
+  // Make sure TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE) is called before 
exiting function.
+  ts::PostScript ps([=]() -> void { TSHttpTxnReenable(txn, 
TS_EVENT_HTTP_CONTINUE); });
+
+  TSReleaseAssert(event == TS_EVENT_HTTP_READ_RESPONSE_HDR);
+
+  auto        &auxData = AuxDataMgr::data(txn);
+  BodyBuilder *data    = auxData.body_builder.get();
+
+  // Only resolve encoding for probe-full-json.
+  if (data == nullptr || data->probe_type != ProbeType::PROBE_FULL_JSON) {
+    return TS_EVENT_NONE;
+  }
+
+  if (TSHttpTxnServerRespGet(txn, &buffer, &hdr) == TS_ERROR) {
+    return TS_EVENT_NONE;
+  }
+
+  ct_field = TSMimeHdrFieldFind(buffer, hdr, TS_MIME_FIELD_CONTENT_TYPE, 
TS_MIME_LEN_CONTENT_TYPE);
+  if (TS_NULL_MLOC != ct_field) {
+    int         ct_len   = 0;
+    const char *ct_value = TSMimeHdrFieldValueStringGet(buffer, hdr, ct_field, 
-1, &ct_len);
+    if (ct_value != nullptr && ct_len > 0) {
+      if (xdebug::is_textual_content_type(std::string_view{ct_value, 
static_cast<size_t>(ct_len)})) {
+        Dbg(dbg_ctl_xform, "Content-type of \"%.*s\": is textual, using escape 
encoding", ct_len, ct_value);
+        data->body_encoding = BodyEncoding_t::ESCAPE;
+      } else {
+        Dbg(dbg_ctl_xform, "Content-type of \"%.*s\": is not textual, using 
hex encoding", ct_len, ct_value);
+        data->body_encoding = BodyEncoding_t::HEX;
+      }
+    }
+    TSHandleMLocRelease(buffer, hdr, ct_field);
+  }
+
+  TSHandleMLocRelease(buffer, TS_NULL_MLOC, hdr);
+  return TS_EVENT_NONE;
+}
+
 // Scan the client request headers and determine which debug headers they
 // want in the response.
 static int
@@ -691,8 +739,14 @@ XScanRequestHeaders(TSCont /* contp */, TSEvent event, 
void *edata)
       // A couple convenience variables for probing.
       bool const is_probe_headers =
         header_field_eq(HEADER_NAME_X_PROBE_HEADERS, value, vsize) && 
(XHEADER_X_PROBE_HEADERS & allowedHeaders);
-      bool const is_probe_full_json =
-        header_field_eq(HEADER_NAME_X_PROBE_FULL_JSON, value, vsize) && 
(XHEADER_X_PROBE_FULL_JSON & allowedHeaders);
+      bool                   is_probe_full_json = false;
+      xdebug::BodyEncoding_t requested_encoding = xdebug::BodyEncoding_t::AUTO;
+      if (XHEADER_X_PROBE_FULL_JSON & allowedHeaders) {
+        swoc::TextView tv{value, static_cast<size_t>(vsize)};
+        if (xdebug::parse_probe_full_json_field_value(tv, requested_encoding)) 
{
+          is_probe_full_json = true;
+        }
+      }
 
       if (header_field_eq(HEADER_NAME_X_CACHE_KEY, value, vsize)) {
         xheaders |= XHEADER_X_CACHE_KEY & allowedHeaders;
@@ -727,6 +781,9 @@ XScanRequestHeaders(TSCont /* contp */, TSEvent event, void 
*edata)
         // prefix request headers and postfix response headers
         BodyBuilder *data = new BodyBuilder();
         data->probe_type  = is_probe_full_json ? ProbeType::PROBE_FULL_JSON : 
ProbeType::PROBE_STANDARD;
+        if (is_probe_full_json) {
+          data->body_encoding = requested_encoding;
+        }
         auxData.body_builder.reset(data);
 
         TSVConn connp = TSTransformCreate(body_transform, txn);
@@ -734,6 +791,15 @@ XScanRequestHeaders(TSCont /* contp */, TSEvent event, 
void *edata)
         TSContDataSet(connp, txn);
         TSHttpTxnHookAdd(txn, TS_HTTP_RESPONSE_TRANSFORM_HOOK, connp);
 
+        // Add hook to resolve encoding before body transformation (unless
+        // explicitly set via =hex, etc.).
+        if (is_probe_full_json && data->body_encoding == BodyEncoding_t::AUTO) 
{
+          TSVConn encoding_connp = TSTransformCreate(XResolveEncoding, txn);
+          data->resolve_encoding_connp.reset(encoding_connp);
+          TSContDataSet(encoding_connp, txn);
+          TSHttpTxnHookAdd(txn, TS_HTTP_READ_RESPONSE_HDR_HOOK, 
encoding_connp);
+        }
+
         // disable writing to cache because we are injecting data into the 
body.
         TSHttpTxnCntlSet(txn, TS_HTTP_CNTL_RESPONSE_CACHEABLE, false);
         TSHttpTxnCntlSet(txn, TS_HTTP_CNTL_REQUEST_CACHEABLE, false);
diff --git a/plugins/xdebug/xdebug_transforms.cc 
b/plugins/xdebug/xdebug_transforms.cc
index 8369e30b30..d400734d0b 100644
--- a/plugins/xdebug/xdebug_transforms.cc
+++ b/plugins/xdebug/xdebug_transforms.cc
@@ -20,9 +20,11 @@
 #include "xdebug_types.h"
 #include "xdebug_headers.h"
 
+#include <array>
 #include <unistd.h>
 #include <sstream>
 #include <cinttypes>
+#include <string>
 
 #include "ts/ts.h"
 
@@ -82,11 +84,101 @@ getPostBodyFullJson(TSHttpTxn txn)
   return output.str();
 }
 
+static inline int64_t
+write_hex(TSIOBuffer output_buffer, const char *src, int64_t len)
+{
+  int64_t written = 0;
+  // Convert each byte to two hex characters
+  static const char hex_chars[] = "0123456789abcdef";
+
+  // Process in chunks to keep stack usage reasonable
+  constexpr int64_t CHUNK = 1024; // 1KB of raw -> 2KB hex
+  int64_t           idx   = 0;
+
+  while (idx < len) {
+    std::array<char, CHUNK * 2> hex_output;
+    int64_t const               num_to_take  = std::min(len - idx, CHUNK);
+    int64_t const               num_to_write = num_to_take * 2;
+    TSReleaseAssert(static_cast<size_t>(num_to_write) <= hex_output.size());
+    for (int64_t i = 0; i < num_to_take; ++i) {
+      unsigned char src_byte = static_cast<unsigned char>(src[idx + i]);
+      hex_output[i * 2]      = hex_chars[(src_byte >> 4) & 0x0F];
+      hex_output[i * 2 + 1]  = hex_chars[src_byte & 0x0F];
+    }
+    TSIOBufferWrite(output_buffer, hex_output.data(), num_to_write);
+    written += num_to_write;
+    idx     += num_to_take;
+  }
+  return written;
+}
+
+/** JSON-escape the given input stream. */
+static inline void
+write_json_escaped(TSIOBuffer output_buffer, const char *data, int64_t len, 
int64_t &written)
+{
+  for (int64_t i = 0; i < len; ++i) {
+    unsigned char c = static_cast<unsigned char>(data[i]);
+    switch (c) {
+    case '"': {
+      const char *s = "\\\"";
+      TSIOBufferWrite(output_buffer, s, 2);
+      written += 2;
+      break;
+    }
+    case '\\': {
+      const char *s = "\\\\";
+      TSIOBufferWrite(output_buffer, s, 2);
+      written += 2;
+      break;
+    }
+    case '\b': {
+      const char *s = "\\b";
+      TSIOBufferWrite(output_buffer, s, 2);
+      written += 2;
+      break;
+    }
+    case '\f': {
+      const char *s = "\\f";
+      TSIOBufferWrite(output_buffer, s, 2);
+      written += 2;
+      break;
+    }
+    case '\n': {
+      const char *s = "\\n";
+      TSIOBufferWrite(output_buffer, s, 2);
+      written += 2;
+      break;
+    }
+    case '\r': {
+      const char *s = "\\r";
+      TSIOBufferWrite(output_buffer, s, 2);
+      written += 2;
+      break;
+    }
+    case '\t': {
+      const char *s = "\\t";
+      TSIOBufferWrite(output_buffer, s, 2);
+      written += 2;
+      break;
+    }
+    default:
+      if (c < 0x20) {
+        written += write_hex(output_buffer, reinterpret_cast<const char 
*>(&c), 1);
+        break;
+      } else {
+        TSIOBufferWrite(output_buffer, reinterpret_cast<const char *>(&c), 1);
+        written += 1;
+      }
+    }
+  }
+}
+
 void
 writePostBody(TSHttpTxn txn, BodyBuilder *data)
 {
   if (data->wrote_body && data->hdr_ready && 
!data->wrote_postbody.test_and_set()) {
     Dbg(dbg_ctl_xform, "body_transform(): Writing postbody headers...");
+    // No cleanup needed for hex encoding - it processes all bytes immediately.
     std::string postbody;
     if (data->probe_type == ProbeType::PROBE_STANDARD) {
       postbody = getPostBody(txn);
@@ -146,6 +238,8 @@ body_transform(TSCont contp, TSEvent event, void * /* edata 
ATS_UNUSED */)
       TSIOBufferWrite(data->output_buffer.get(), prebody.data(), 
prebody.length()); // write prebody
       data->wrote_prebody  = true;
       data->nbytes        += prebody.length();
+      Dbg(dbg_ctl_xform, "Pre body content done, body will be %s",
+          data->body_encoding == BodyEncoding_t::ESCAPE ? "escaped" : 
"hex-encoded");
     }
 
     TSIOBuffer src_buf = TSVIOBufferGet(src_vio);
@@ -162,10 +256,43 @@ body_transform(TSCont contp, TSEvent event, void * /* 
edata ATS_UNUSED */)
     int64_t avail = TSIOBufferReaderAvail(TSVIOReaderGet(src_vio));
     towrite       = towrite > avail ? avail : towrite;
     if (towrite > 0) {
-      TSIOBufferCopy(TSVIOBufferGet(data->output_vio), 
TSVIOReaderGet(src_vio), towrite, 0);
-      TSIOBufferReaderConsume(TSVIOReaderGet(src_vio), towrite);
-      TSVIONDoneSet(src_vio, TSVIONDoneGet(src_vio) + towrite);
-      Dbg(dbg_ctl_xform, "body_transform(): writing %" PRId64 " bytes of 
body", towrite);
+      if (data->probe_type == ProbeType::PROBE_STANDARD) {
+        TSIOBufferCopy(TSVIOBufferGet(data->output_vio), 
TSVIOReaderGet(src_vio), towrite, 0);
+        TSIOBufferReaderConsume(TSVIOReaderGet(src_vio), towrite);
+        TSVIONDoneSet(src_vio, TSVIONDoneGet(src_vio) + towrite);
+        Dbg(dbg_ctl_xform, "body_transform(): writing %" PRId64 " bytes of 
body (standard)", towrite);
+      } else {
+        // Encode and write into our output buffer for full-json.
+        int64_t          remaining  = towrite;
+        TSIOBufferReader src_reader = TSVIOReaderGet(src_vio);
+        while (remaining > 0) {
+          TSIOBufferBlock src_block;
+          int64_t         src_block_avail = 0;
+          src_block                       = TSIOBufferReaderStart(src_reader);
+          const char *src_block_start     = 
TSIOBufferBlockReadStart(src_block, src_reader, &src_block_avail);
+          if (src_block_avail <= 0)
+            break;
+          int64_t take      = src_block_avail > remaining ? remaining : 
src_block_avail;
+          int64_t wrote_now = 0;
+          switch (data->body_encoding) {
+          case BodyEncoding_t::OMIT_BODY:
+            wrote_now = 0;
+            break;
+          case BodyEncoding_t::ESCAPE:
+            write_json_escaped(data->output_buffer.get(), src_block_start, 
take, wrote_now);
+            break;
+          case BodyEncoding_t::HEX:
+          case BodyEncoding_t::AUTO: // AUTO should have been resolved in 
header phase, fallback to HEX.
+            wrote_now += write_hex(data->output_buffer.get(), src_block_start, 
take);
+            break;
+          }
+          data->nbytes += wrote_now;
+          TSIOBufferReaderConsume(src_reader, take);
+          TSVIONDoneSet(src_vio, TSVIONDoneGet(src_vio) + take);
+          remaining -= take;
+        }
+        Dbg(dbg_ctl_xform, "body_transform(): consumed %" PRId64 " bytes of 
origin body (encoded)", towrite);
+      }
     }
 
     if (TSVIONTodoGet(src_vio) > 0) {
@@ -174,8 +301,10 @@ body_transform(TSCont contp, TSEvent event, void * /* 
edata ATS_UNUSED */)
     } else {
       // End of src vio
       // Write post body content and update output VIO
-      data->wrote_body  = true;
-      data->nbytes     += TSVIONDoneGet(src_vio);
+      data->wrote_body = true;
+      if (data->probe_type == ProbeType::PROBE_STANDARD) {
+        data->nbytes += TSVIONDoneGet(src_vio);
+      }
       writePostBody(txn, data);
       TSContCall(TSVIOContGet(src_vio), TS_EVENT_VCONN_WRITE_COMPLETE, 
src_vio);
     }
diff --git a/plugins/xdebug/xdebug_types.h b/plugins/xdebug/xdebug_types.h
index d6f696feea..718e46e74e 100644
--- a/plugins/xdebug/xdebug_types.h
+++ b/plugins/xdebug/xdebug_types.h
@@ -32,8 +32,19 @@ namespace xdebug
 
 enum class ProbeType { PROBE_STANDARD, PROBE_FULL_JSON };
 
+/** Encoding strategy for embedding the origin server body in probe-full-json
+ * output.
+ */
+enum class BodyEncoding_t {
+  AUTO,      ///< Auto-detect the encoding based on the original response 
Content-Type.
+  ESCAPE,    ///< JSON-escape the body.
+  HEX,       ///< Hex-encode the body, treating it as binary data.
+  OMIT_BODY, ///< Omit the body entirely.
+};
+
 struct BodyBuilder {
   atscppapi::TSContUniqPtr     transform_connp;
+  atscppapi::TSContUniqPtr     resolve_encoding_connp;
   atscppapi::TSIOBufferUniqPtr output_buffer;
   // It's important that output_reader comes after output_buffer so it will be 
deleted first.
   atscppapi::TSIOBufferReaderUniqPtr output_reader;
@@ -44,6 +55,9 @@ struct BodyBuilder {
   std::atomic_flag                   wrote_postbody;
   ProbeType                          probe_type = ProbeType::PROBE_STANDARD;
 
+  BodyEncoding_t body_encoding       = BodyEncoding_t::AUTO;
+  bool           server_body_started = false;
+
   int64_t nbytes = 0;
 };
 
diff --git a/plugins/xdebug/xdebug_utils.cc b/plugins/xdebug/xdebug_utils.cc
new file mode 100644
index 0000000000..7e8b0933fd
--- /dev/null
+++ b/plugins/xdebug/xdebug_utils.cc
@@ -0,0 +1,108 @@
+/** @file
+ *
+ * XDebug plugin utility functions implementation.
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#include "xdebug_utils.h"
+#include "xdebug_types.h"
+#include <swoc/TextView.h>
+#include <cctype>
+#include <cstring>
+
+namespace xdebug
+{
+
+bool
+parse_probe_full_json_field_value(std::string_view value, BodyEncoding_t 
&encoding)
+{
+  swoc::TextView tv = value;
+  tv.trim_if(isspace);
+  if (!tv.starts_with_nocase("probe-full-json"))
+    return false;
+  encoding = BodyEncoding_t::AUTO;
+  if (tv.size() == strlen("probe-full-json"))
+    return true; // No suffix.
+  tv.remove_prefix(strlen("probe-full-json"));
+  tv.trim_if(isspace);
+  if (!tv)
+    return true; // No suffix.
+  if (tv.front() != '=')
+    return false; // Unrecognized suffix.
+  tv.remove_prefix(1);
+  tv.trim_if(isspace);
+  swoc::TextView suffix = tv; // whole remainder
+  if (suffix.starts_with_nocase("hex") && suffix.size() == 3) {
+    encoding = BodyEncoding_t::HEX;
+  } else if (suffix.starts_with_nocase("escape") && suffix.size() == 6) {
+    encoding = BodyEncoding_t::ESCAPE;
+  } else if (suffix.starts_with_nocase("nobody") && suffix.size() == 6) {
+    encoding = BodyEncoding_t::OMIT_BODY;
+  } else {
+    return false; // Unrecognized suffix.
+  }
+  return true;
+}
+
+bool
+is_textual_content_type(std::string_view ct)
+{
+  swoc::TextView content_type = ct;
+  content_type.trim_if(isspace);
+
+  // Helper to check case-insensitive substring containment
+  auto contains_nocase = [&content_type](std::string_view needle) -> bool {
+    swoc::TextView remaining = content_type;
+    while (remaining.size() >= needle.size()) {
+      if (remaining.starts_with_nocase(needle)) {
+        return true;
+      }
+      remaining.remove_prefix(1);
+    }
+    return false;
+  };
+
+  // Check for text/ prefix (case insensitive)
+  if (content_type.starts_with_nocase("text/")) {
+    return true;
+  }
+
+  // Check for common textual content indicators (case insensitive)
+  if (contains_nocase("json")) {
+    return true;
+  }
+  if (contains_nocase("xml")) {
+    return true;
+  }
+  if (contains_nocase("javascript")) {
+    return true;
+  }
+  if (contains_nocase("csv")) {
+    return true;
+  }
+  if (contains_nocase("html")) {
+    return true;
+  }
+  if (contains_nocase("plain")) {
+    return true;
+  }
+
+  return false;
+}
+
+} // namespace xdebug
diff --git a/plugins/xdebug/xdebug_utils.h b/plugins/xdebug/xdebug_utils.h
new file mode 100644
index 0000000000..a33ee22626
--- /dev/null
+++ b/plugins/xdebug/xdebug_utils.h
@@ -0,0 +1,57 @@
+/** @file
+ *
+ * XDebug plugin utility functions.
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#pragma once
+
+#include <string_view>
+
+namespace xdebug
+{
+
+enum class BodyEncoding_t;
+
+/** Parse the probe-full-json header field value.
+ *
+ * @param[in] value The header field value to parse.
+ * @param[out] encoding The encoding to set.
+ * @return True if the header field value was parsed successfully, false 
otherwise.
+ *
+ * Supports formats:
+ * - "probe-full-json" -> encoding = AUTO, returns true
+ * - "probe-full-json=b64" -> encoding = BASE64, returns true
+ * - "probe-full-json=escape" -> encoding = ESCAPE, returns true
+ * - "probe-full-json=nobody" -> encoding = OMIT_BODY, returns true
+ * - Invalid formats return false
+ */
+bool parse_probe_full_json_field_value(std::string_view value, BodyEncoding_t 
&encoding);
+
+/** Check if a content-type string represents textual content.
+ *
+ * @param ct The content-type string to check.
+ * @return True if the content-type represents textual content.
+ *
+ * Considers the following as textual:
+ * - Starts with "text/"
+ * - Contains "json", "xml", "javascript", "csv", "html", or "plain"
+ */
+bool is_textual_content_type(std::string_view ct);
+
+} // namespace xdebug
diff --git a/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq.gold 
b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq.gold
deleted file mode 100644
index 27fb0a3b1b..0000000000
--- a/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq.gold
+++ /dev/null
@@ -1,3 +0,0 @@
-"1"
-"Original server response"
-"from-origin"
diff --git 
a/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq_escaped.gold 
b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq_escaped.gold
new file mode 100644
index 0000000000..2afdc3f2b9
--- /dev/null
+++ b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq_escaped.gold
@@ -0,0 +1,3 @@
+"1"
+"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n   
 <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n   
 <title>Test Page with \"Quotes\"</title>\n    <style>\n        body { 
font-family: Arial, sans-serif; margin: 20px; }\n        .highlight { color: 
\"red\"; background: \"yellow\"; }\n    </style>\n</head>\n<body>\n    
<header>\n        <h1>Welcome to the \"Test\" Page</h1>\n        <p>This page 
contains \"double quotes\" that will [...]
+"from-origin"
diff --git 
a/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq_hex.gold 
b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq_hex.gold
new file mode 100644
index 0000000000..1740eda1a5
--- /dev/null
+++ b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq_hex.gold
@@ -0,0 +1,3 @@
+"2"
+"42696e617279206461746120776974682071756f746573210000"
+"binary-data"
diff --git 
a/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq_nobody.gold 
b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq_nobody.gold
new file mode 100644
index 0000000000..753dfafee4
--- /dev/null
+++ b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/gold/jq_nobody.gold
@@ -0,0 +1,3 @@
+"1"
+""
+"from-origin"
diff --git 
a/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/x_probe_full_json.replay.yaml
 
b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/x_probe_full_json.replay.yaml
index 7e32f984cd..cb86b789c4 100644
--- 
a/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/x_probe_full_json.replay.yaml
+++ 
b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/x_probe_full_json.replay.yaml
@@ -39,12 +39,46 @@ sessions:
       headers:
         fields:
         - [ Content-Type, "text/html" ]
-        - [ Content-Length, "24" ]
+        - [ Transfer-Encoding, "chunked" ]
         - [ X-Response, "from-origin" ]
         - [ X-No-Value, "" ]
       content:
         encoding: plain
-        data: "Original server response"
+        data: |
+          <!DOCTYPE html>
+          <html lang="en">
+          <head>
+              <meta charset="UTF-8">
+              <meta name="viewport" content="width=device-width, 
initial-scale=1.0">
+              <title>Test Page with "Quotes"</title>
+              <style>
+                  body { font-family: Arial, sans-serif; margin: 20px; }
+                  .highlight { color: "red"; background: "yellow"; }
+              </style>
+          </head>
+          <body>
+              <header>
+                  <h1>Welcome to the "Test" Page</h1>
+                  <p>This page contains "double quotes" that will break JSON 
parsing.</p>
+              </header>
+              <main>
+                  <h2>Content with "Problematic" Characters</h2>
+                  <p>Here are some examples:</p>
+                  <ul>
+                      <li>Attribute with quotes: class="example"</li>
+                      <li>String with quotes: "Hello World"</li>
+                      <li>Nested quotes: "She said \"Hello\" to me"</li>
+                  </ul>
+                  <script>
+                      var message = "This script contains \"quotes\" that will 
break JSON";
+                      console.log("Debug: " + message);
+                  </script>
+              </main>
+              <footer>
+                  <p>&copy; 2024 "Test Company" - All rights reserved.</p>
+              </footer>
+          </body>
+          </html>
 
     proxy-response:
       status: 200
@@ -54,5 +88,45 @@ sessions:
         - [ X-Response, { value: "from-origin", as: contains } ]
         - [ X-Original-Content-Type, { value: "text/html", as: equal } ]
       content:
-        data: "Original server response"
+        data: 'Welcome to the \"Test\" Page'
+        verify: { as: contains }
+
+  # Test 2: Binary content type (should be hex-encoded by default)
+  - client-request:
+      method: GET
+      url: /binary
+      version: '1.1'
+      headers:
+        fields:
+        - [ uuid, '2' ]
+        - [ Host, example.com ]
+        - [ X-Debug, probe-full-json ]
+        - [ X-Request, "binary-test"]
+
+    proxy-request:
+      headers:
+        fields:
+        - [ x-debug, { as: absent } ]
+
+    server-response:
+      status: 200
+      headers:
+        fields:
+        - [ Content-Type, "application/octet-stream" ]
+        - [ Content-Length, "26" ]
+        - [ X-Response, "binary-data" ]
+      content:
+        encoding: plain
+        data: "Binary data with quotes!"
+
+    proxy-response:
+      status: 200
+      headers:
+        fields:
+        - [ Content-Type, { value: "application/json", as: equal } ]
+        - [ X-Response, { value: "binary-data", as: contains } ]
+        - [ X-Original-Content-Type, { value: "application/octet-stream", as: 
equal } ]
+      content:
+        # Expect hex encoding: "Binary data with quotes!" -> hex
+        data: '42696e617279206461746120776974682071756f74657321'
         verify: { as: contains }
diff --git 
a/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/x_probe_full_json.test.py
 
b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/x_probe_full_json.test.py
index 4f92e423f3..5b69bf08a5 100644
--- 
a/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/x_probe_full_json.test.py
+++ 
b/tests/gold_tests/pluginTest/xdebug/x_probe_full_json/x_probe_full_json.test.py
@@ -29,7 +29,7 @@ class XDebugProbeFullJsonTest:
     to include request headers, response body, and response headers in a 
complete JSON format.
 
     The probe-full-json feature:
-    - Changes Content-Type to text/plain
+    - Changes Content-Type to application/json
     - Generates a complete JSON object containing all debug information
     - Includes client/server request headers, response body, and client/server 
response headers
     - Disables caching due to body modification
@@ -79,19 +79,59 @@ class XDebugProbeFullJsonTest:
         tr.AddVerifierClientProcess("client", self._replay_file, 
http_ports=[self._ts.Variables.port])
         tr.Processes.Default.ReturnCode = 0
         tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
-            'X-Original-Content-Type', "X-Original-Content-Type should be 
present")
+            'X-Original-Content-Type: text/html', "X-Original-Content-Type of 
text/html should be present")
+        tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
+            'X-Original-Content-Type: application/octet-stream',
+            "X-Original-Content-Type of application/octet-stream should be 
present")
 
     def _setupJqValidation(self) -> None:
         """Use curl to get the response body and pipe through jq to validate 
JSON.
         """
-        tr = Test.AddTestRun("Verify JSON output")
+        tr = Test.AddTestRun("Escaped text/html content.")
         self._startServersIfNeeded(tr)
         tr.MakeCurlCommand(
             f'-s -H"uuid: 1" -H "Host: example.com" -H "X-Debug: 
probe-full-json" '
             f'http://127.0.0.1:{self._ts.Variables.port}/test | '
             "jq 
'.\"client-request\".\"uuid\",.\"server-body\",.\"proxy-response\".\"x-response\"'")
         tr.Processes.Default.ReturnCode = 0
-        tr.Processes.Default.Streams.stdout = "gold/jq.gold"
+        tr.Processes.Default.Streams.stdout = "gold/jq_escaped.gold"
+
+        tr = Test.AddTestRun("Hex-encoded application/octet-stream content.")
+        self._startServersIfNeeded(tr)
+        tr.MakeCurlCommand(
+            f'-s -H"uuid: 2" -H "Host: example.com" -H "X-Debug: 
probe-full-json" '
+            f'http://127.0.0.1:{self._ts.Variables.port}/binary | '
+            "jq 
'.\"client-request\".\"uuid\",.\"server-body\",.\"proxy-response\".\"x-response\"'")
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.Streams.stdout += "gold/jq_hex.gold"
+
+        tr = Test.AddTestRun("=hex")
+        self._startServersIfNeeded(tr)
+        tr.MakeCurlCommand(
+            f'-s -H"uuid: 1" -H "Host: example.com" -H "X-Debug: 
probe-full-json=hex" '
+            f'http://127.0.0.1:{self._ts.Variables.port}/test | '
+            "jq '.\"server-body\"'")
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
+            '3c21444f43545950452068746d6c3e', "Should contain hex-encoded HTML 
content (forced hex override)")
+
+        tr = Test.AddTestRun("=escape")
+        self._startServersIfNeeded(tr)
+        tr.MakeCurlCommand(
+            f'-s -H"uuid: 1" -H "Host: example.com" -H "X-Debug: 
probe-full-json=escape" '
+            f'http://127.0.0.1:{self._ts.Variables.port}/test | '
+            "jq 
'.\"client-request\".\"uuid\",.\"server-body\",.\"proxy-response\".\"x-response\"'")
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.Streams.stdout += "gold/jq_escaped.gold"
+
+        tr = Test.AddTestRun("=nobody")
+        self._startServersIfNeeded(tr)
+        tr.MakeCurlCommand(
+            f'-s -H"uuid: 1" -H "Host: example.com" -H "X-Debug: 
probe-full-json=nobody" '
+            f'http://127.0.0.1:{self._ts.Variables.port}/test | '
+            "jq 
'.\"client-request\".\"uuid\",.\"server-body\",.\"proxy-response\".\"x-response\"'")
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.Streams.stdout += "gold/jq_nobody.gold"
 
 
 # Execute the test


Reply via email to