diff --git a/appsec/src/extension/commands/request_shutdown.c b/appsec/src/extension/commands/request_shutdown.c
index dc845e665c..2b7b050418 100644
--- a/appsec/src/extension/commands/request_shutdown.c
+++ b/appsec/src/extension/commands/request_shutdown.c
@@ -6,6 +6,7 @@
#include "request_shutdown.h"
#include "../commands_helpers.h"
+#include "../configuration.h"
#include "../ddappsec.h"
#include "../ddtrace.h"
#include "../entity_body.h"
@@ -62,7 +63,12 @@ static dd_result _request_pack(mpack_writer_t *nonnull w, void *nonnull ctx)
}
}
- mpack_start_map(w, 2 + (Z_TYPE(resp_body) != IS_NULL ? 1 : 0));
+ bool send_raw_body = get_global_DD_APPSEC_RAW_RESPONSE_BODY_ENABLED() &&
+ req_info->entity != NULL && req_info->entity->len > 0;
+
+ uint32_t num_entries =
+ 2 + (Z_TYPE(resp_body) != IS_NULL ? 1 : 0) + (send_raw_body ? 1 : 0);
+ mpack_start_map(w, num_entries);
// 1.1.
{
@@ -94,6 +100,13 @@ static dd_result _request_pack(mpack_writer_t *nonnull w, void *nonnull ctx)
}
}
+ // 1.4.?
+ if (send_raw_body) {
+ dd_mpack_write_lstr(w, "server.response.body.raw");
+ mpack_write_str(w, ZSTR_VAL(req_info->entity),
+ (uint32_t)ZSTR_LEN(req_info->entity));
+ }
+
mpack_finish_map(w);
// 2.
diff --git a/appsec/src/extension/configuration.h b/appsec/src/extension/configuration.h
index 641c073ba4..8e41475d0a 100644
--- a/appsec/src/extension/configuration.h
+++ b/appsec/src/extension/configuration.h
@@ -62,6 +62,7 @@ extern bool runtime_config_first_init;
CONFIG(STRING, DD_AGENT_HOST, "localhost") \
CONFIG(INT, DD_TRACE_AGENT_PORT, "0") \
CONFIG(INT, DD_APPSEC_MAX_BODY_BUFF_SIZE, "524288") \
+ SYSCFG(BOOL, DD_APPSEC_RAW_RESPONSE_BODY_ENABLED, "false") \
CONFIG(STRING, DD_TRACE_AGENT_URL, "") \
CONFIG(BOOL, DD_TRACE_ENABLED, "true") \
CALIAS(CUSTOM(STRING), DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE, "ident", \
diff --git a/appsec/tests/extension/rshutdown_command_raw_body_disabled.phpt b/appsec/tests/extension/rshutdown_command_raw_body_disabled.phpt
new file mode 100644
index 0000000000..1cab4af123
--- /dev/null
+++ b/appsec/tests/extension/rshutdown_command_raw_body_disabled.phpt
@@ -0,0 +1,33 @@
+--TEST--
+request_shutdown — server.response.body.raw not sent when feature is disabled (default)
+--INI--
+expose_php=0
+datadog.appsec.enabled=1
+--GET--
+a=b
+--FILE--
+get_commands(); // ignore
+
+var_dump(rshutdown());
+$c = $helper->get_commands();
+var_dump(array_key_exists('server.response.body.raw', $c[0][1][0]));
+?>
+--EXPECT--
+bool(true)
+plain text body
+bool(true)
+bool(false)
diff --git a/appsec/tests/extension/rshutdown_command_raw_body_json.phpt b/appsec/tests/extension/rshutdown_command_raw_body_json.phpt
new file mode 100644
index 0000000000..c2e6b50d33
--- /dev/null
+++ b/appsec/tests/extension/rshutdown_command_raw_body_json.phpt
@@ -0,0 +1,46 @@
+--TEST--
+request_shutdown — server.response.body.raw sent alongside parsed server.response.body for JSON
+--INI--
+expose_php=0
+datadog.appsec.enabled=1
+datadog.appsec.raw_response_body_enabled=1
+--GET--
+a=b
+--FILE--
+get_commands(); // ignore
+
+var_dump(rshutdown());
+$c = $helper->get_commands();
+$data = $c[0][1][0];
+
+// both structured and raw body are present
+var_dump(isset($data['server.response.body']));
+var_dump(isset($data['server.response.body.raw']));
+print_r($data['server.response.body']);
+echo $data['server.response.body.raw'];
+?>
+--EXPECT--
+bool(true)
+{"key":"value"}
+bool(true)
+bool(true)
+bool(true)
+Array
+(
+ [key] => value
+)
+{"key":"value"}
diff --git a/appsec/tests/extension/rshutdown_command_raw_body_plain_text.phpt b/appsec/tests/extension/rshutdown_command_raw_body_plain_text.phpt
new file mode 100644
index 0000000000..f1d4a73151
--- /dev/null
+++ b/appsec/tests/extension/rshutdown_command_raw_body_plain_text.phpt
@@ -0,0 +1,42 @@
+--TEST--
+request_shutdown — server.response.body.raw sent for non-JSON/XML content types
+--INI--
+expose_php=0
+datadog.appsec.enabled=1
+datadog.appsec.raw_response_body_enabled=1
+--GET--
+a=b
+--FILE--
+get_commands(); // ignore
+
+var_dump(rshutdown());
+$c = $helper->get_commands();
+$data = $c[0][1][0];
+
+// server.response.body is absent for non-JSON/XML
+var_dump(isset($data['server.response.body']));
+// server.response.body.raw is present with the raw text
+var_dump(isset($data['server.response.body.raw']));
+echo $data['server.response.body.raw'];
+?>
+--EXPECT--
+bool(true)
+plain text body
+bool(true)
+bool(false)
+bool(true)
+plain text body
diff --git a/appsec/tests/extension/rshutdown_command_raw_body_xml.phpt b/appsec/tests/extension/rshutdown_command_raw_body_xml.phpt
new file mode 100644
index 0000000000..f57c54a313
--- /dev/null
+++ b/appsec/tests/extension/rshutdown_command_raw_body_xml.phpt
@@ -0,0 +1,61 @@
+--TEST--
+request_shutdown — server.response.body.raw sent alongside parsed server.response.body for XML
+--INI--
+expose_php=0
+datadog.appsec.enabled=1
+datadog.appsec.raw_response_body_enabled=1
+--GET--
+a=b
+--FILE--
+
+baz
+XML;
+echo "$xml\n";
+$helper->get_commands(); // ignore
+
+var_dump(rshutdown());
+$c = $helper->get_commands();
+$data = $c[0][1][0];
+
+// both structured and raw body are present
+var_dump(isset($data['server.response.body']));
+var_dump(isset($data['server.response.body.raw']));
+print_r($data['server.response.body']);
+echo $data['server.response.body.raw'];
+?>
+--EXPECT--
+bool(true)
+
+baz
+bool(true)
+bool(true)
+bool(true)
+Array
+(
+ [foo] => Array
+ (
+ [0] => Array
+ (
+ [@attr] => bar
+ )
+
+ [1] => baz
+ )
+
+)
+
+baz
diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json
index 361057fad8..5519b28686 100644
--- a/metadata/supported-configurations.json
+++ b/metadata/supported-configurations.json
@@ -186,6 +186,13 @@
"default": "true"
}
],
+ "DD_APPSEC_RAW_RESPONSE_BODY_ENABLED": [
+ {
+ "implementation": "A",
+ "type": "boolean",
+ "default": "false"
+ }
+ ],
"DD_APPSEC_RULES": [
{
"implementation": "C",
diff --git a/zend_abstract_interface/config/config.h b/zend_abstract_interface/config/config.h
index d7598cf35f..0bf724bc28 100644
--- a/zend_abstract_interface/config/config.h
+++ b/zend_abstract_interface/config/config.h
@@ -18,7 +18,7 @@ typedef uint16_t zai_config_id;
#include "config_ini.h"
#include "config_stable_file.h"
-#define ZAI_CONFIG_ENTRIES_COUNT_MAX 300
+#define ZAI_CONFIG_ENTRIES_COUNT_MAX 350
#define ZAI_CONFIG_NAMES_COUNT_MAX 4
#define ZAI_CONFIG_NAME_BUFSIZ 72