From 3c2d8c25819ac7e16b1799c3de3c6ac6bb680f58 Mon Sep 17 00:00:00 2001 From: Osk Date: Wed, 6 Dec 2023 11:11:28 -0300 Subject: [PATCH] Debug Helper: Add XML-RPC logger module (#34487) --- .../add-xmlrpc-logger-jetpack-debug-helper | 4 + .../modules/class-xmlrpc-logger.php | 254 ++++++++++++++++++ projects/plugins/debug-helper/plugin.php | 5 + 3 files changed, 263 insertions(+) create mode 100644 projects/plugins/debug-helper/changelog/add-xmlrpc-logger-jetpack-debug-helper create mode 100644 projects/plugins/debug-helper/modules/class-xmlrpc-logger.php diff --git a/projects/plugins/debug-helper/changelog/add-xmlrpc-logger-jetpack-debug-helper b/projects/plugins/debug-helper/changelog/add-xmlrpc-logger-jetpack-debug-helper new file mode 100644 index 0000000000000..d9a4578420c3a --- /dev/null +++ b/projects/plugins/debug-helper/changelog/add-xmlrpc-logger-jetpack-debug-helper @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Added XML-RPC logger module diff --git a/projects/plugins/debug-helper/modules/class-xmlrpc-logger.php b/projects/plugins/debug-helper/modules/class-xmlrpc-logger.php new file mode 100644 index 0000000000000..4a42ad97d5c83 --- /dev/null +++ b/projects/plugins/debug-helper/modules/class-xmlrpc-logger.php @@ -0,0 +1,254 @@ +settings = $this->get_stored_settings(); + // Hook into the WordPress initialization process to log XML-RPC requests. + add_action( 'init', array( $this, 'log_xmlrpc_requests_on_init' ) ); + // Hook into the WordPress admin menu to register the XML-RPC logger submenu page. + add_action( 'admin_menu', array( $this, 'register_submenu_page' ), 1000 ); + } + + /** + * Logs XML-RPC requests. + * Checks if the current request is a POST to xmlrpc.php and logs. + */ + public function log_xmlrpc_requests_on_init() { + if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_SERVER['SCRIPT_FILENAME'] ) && basename( esc_url_raw( wp_unslash( $_SERVER['SCRIPT_FILENAME'] ) ) ) === 'xmlrpc.php' + && $this->settings['log_incoming_xmlrpc_requests'] + ) { + $this->log_xmlrpc_request(); + } + } + + /** + * Registers the XML-RPC logger submenu page. + */ + public function register_submenu_page() { + add_submenu_page( + 'jetpack-debug-tools', + 'XML-RPC Logger', + 'XML-RPC Logger', + 'manage_options', + 'jetpack_xmlrpc_logger', + array( $this, 'render_submenu_page' ) + ); + } + + /** + * Retrieves the stored XML-RPC logger settings. + * + * @return array The stored XML-RPC logger settings. + */ + public function get_stored_settings() { + $defaults = array( + 'log_incoming_xmlrpc_requests' => true, + 'log_xmlrpc_requests_as_json' => false, + ); + $settings = get_option( 'jetpack_xmlrpc_logger_settings', $defaults ); + return wp_parse_args( $settings, $defaults ); + } + + /** + * Saves the XML-RPC logger settings. + */ + public function maybe_handle_submit() { + if ( isset( $_POST['save_xmlrpc_logger'] ) ) { + check_admin_referer( 'xmlrpc_logger_nonce' ); + } else { + return; + } + $this->settings = $this->get_stored_settings(); + $this->settings['log_incoming_xmlrpc_requests'] = isset( $_POST['log_incoming_xmlrpc_requests'] ); + $this->settings['log_xmlrpc_requests_as_json'] = isset( $_POST['log_xmlrpc_requests_as_json'] ); + return update_option( 'jetpack_xmlrpc_logger_settings', $this->settings ); + } + + /** + * Renders the XML-RPC logger settings page. + */ + public function render_submenu_page() { + $this->maybe_handle_submit(); + + $log_incoming_xmlrpc_requests_checked = $this->settings['log_incoming_xmlrpc_requests'] ? 'checked="checked"' : ''; + $log_xmlrpc_requests_as_json_checked = $this->settings['log_xmlrpc_requests_as_json'] ? 'checked="checked"' : ''; + ?> +

XML-RPC Logger

+

This module helps you log all incoming XML-RPC requests.

+

All instances of logging are stored in debug.log. Nothing done here will alter or expose sensitive data.

+
+ +

Current XML-RPC options being used by the module:

+
+ + + +
+ + + + + + + + + + + +
+ Log incoming XML-RPC requests + +
+ +
+
+ Log as JSON + +
+ +
+
+ +
+ +
+ +
+
+ methodName ? (string) $xml->methodName : 'Unknown Method'; + $formatted = $this->settings['log_xmlrpc_requests_as_json'] ? $this->convert_xml_rpc_to_json( $xml ) : $this->pretty_print_xml( $xml_string ); + + // Format and log the request + if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { + $log_message = "XML-RPC Request - Method: $method_name\n"; + $log_message .= "Payload:\n" . $formatted . "\n"; + l( $log_message ); + } + } else { + l( 'XML-RPC Request: Invalid XML - ' . libxml_get_errors()[0]->message ); + libxml_clear_errors(); + return; + } + } + + /** + * Pretty prints the XML. + * + * @param string $xml The XML to pretty print. + * @return string The pretty printed XML. + */ + public function pretty_print_xml( $xml ) { + $dom = new \DOMDocument(); + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $dom->preserveWhiteSpace = false; + $dom->loadXML( $xml ); + + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $dom->formatOutput = true; + return $dom->saveXML(); + } + + /** + * Converts the XML-RPC request to JSON. + * + * @param \SimpleXMLElement $xml The XML to convert. + * @return string The JSON string. + */ + public function convert_xml_rpc_to_json( $xml ) { + // Convert SimpleXML object to an array + // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode, WordPress.WP.AlternativeFunctions.json_decode_json_decode + $array = json_decode( json_encode( (array) $xml ), true ); + + // Recursively clean up the array from empty arrays and objects + $array = $this->recursive_array_clean( $array ); + + // Convert the array to a JSON string + // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode + return json_encode( $array, JSON_PRETTY_PRINT ); + } + + /** + * Recursively cleans up an array from empty arrays and objects. + * + * @param array $array The array to clean up. + * @return array The cleaned up array. + */ + public function recursive_array_clean( $array ) { + foreach ( $array as $key => $value ) { + if ( is_array( $value ) ) { + $array[ $key ] = $this->recursive_array_clean( $array[ $key ] ); + } + + // Remove empty arrays and objects + if ( empty( $array[ $key ] ) ) { + unset( $array[ $key ] ); + } + } + + return $array; + } + + /** + * Load the class. + */ + public static function register_xmlrpc_logger() { + new self(); + } +} + +add_action( 'plugins_loaded', array( XMLRPC_Logger::class, 'register_xmlrpc_logger' ), 1000 ); diff --git a/projects/plugins/debug-helper/plugin.php b/projects/plugins/debug-helper/plugin.php index edb9c4b0e284c..de421c3e2dd72 100644 --- a/projects/plugins/debug-helper/plugin.php +++ b/projects/plugins/debug-helper/plugin.php @@ -104,6 +104,11 @@ 'name' => 'WPCOM API Request Tracker', 'description' => 'Displays the number of requests to WPCOM API endpoints for the current page request.', ), + 'xmlrpc-logger' => array( + 'file' => 'class-xmlrpc-logger.php', + 'name' => 'XMLRPC Logger', + 'description' => 'Logs incoming XMLRPC requests into the debug.log file.', + ), ); require_once __DIR__ . '/class-admin.php';