diff --git a/lib/CleantalkSP/SpbctWP/RemoteCalls.php b/lib/CleantalkSP/SpbctWP/RemoteCalls.php index 674ad7990..409dac1e5 100644 --- a/lib/CleantalkSP/SpbctWP/RemoteCalls.php +++ b/lib/CleantalkSP/SpbctWP/RemoteCalls.php @@ -33,11 +33,61 @@ class RemoteCalls extends \CleantalkSP\Common\RemoteCalls 'spbc_key', ]; + /** Default Content-Type for responses */ + const DEFAULT_CONTENT_TYPE = 'text/plain; charset=UTF-8'; + + /** + * Action Content-Type list + * + * @var array + */ + private static $actionContentTypes = [ + // HTML page + 'debug' => 'text/html; charset=UTF-8', + // JSON payloads + 'update_settings' => 'application/json; charset=UTF-8', + 'post_api_key' => 'application/json; charset=UTF-8', + 'license_update' => 'application/json; charset=UTF-8', + 'run_service_template_get' => 'application/json; charset=UTF-8', + 'scanner__check_dir' => 'application/json; charset=UTF-8', + 'update_pscan_statuses' => 'application/json; charset=UTF-8', + ]; + public function __construct(&$state) { $this->without_token = self::checkWithoutToken(); $this->state = $state; $this->class_name = __CLASS__; + + // Declare the response type before any handler outputs (runs only for RC). + self::sendContentTypeHeader(Request::getString('spbc_remote_call_action')); + } + + /** + * Resolve the full Content-Type header value for action. + * + * @param mixed $action + * @return string + */ + public static function resolveContentType($action) + { + $action = strtolower((string)$action); + return isset(self::$actionContentTypes[$action]) + ? self::$actionContentTypes[$action] + : self::DEFAULT_CONTENT_TYPE; + } + + /** + * Send response Content-Type header unless headers have already been sent. + * + * @param mixed $action Remote call action name ('' when absent). + * @return void + */ + private static function sendContentTypeHeader($action) + { + if (! headers_sent()) { + header('Content-Type: ' . self::resolveContentType($action)); + } } /** diff --git a/tests/lib/CleantalkSP/SpbctWP/RemoteCalls/RemoteCallsContentTypeTest.php b/tests/lib/CleantalkSP/SpbctWP/RemoteCalls/RemoteCallsContentTypeTest.php new file mode 100644 index 000000000..8ee964e8f --- /dev/null +++ b/tests/lib/CleantalkSP/SpbctWP/RemoteCalls/RemoteCallsContentTypeTest.php @@ -0,0 +1,64 @@ +assertSame('text/plain; charset=UTF-8', RemoteCalls::DEFAULT_CONTENT_TYPE); + } + + public function testPlainTextActionsFallBackToDefault() + { + $this->assertSame('text/plain; charset=UTF-8', RemoteCalls::resolveContentType('scanner__controller')); + $this->assertSame('text/plain; charset=UTF-8', RemoteCalls::resolveContentType('launch_background_scan')); + $this->assertSame('text/plain; charset=UTF-8', RemoteCalls::resolveContentType('check_website')); + $this->assertSame('text/plain; charset=UTF-8', RemoteCalls::resolveContentType('update_security_firewall__worker')); + } + + public function testUnknownActionFallsBackToDefault() + { + $this->assertSame('text/plain; charset=UTF-8', RemoteCalls::resolveContentType('no_such_action_at_all')); + $this->assertSame('text/plain; charset=UTF-8', RemoteCalls::resolveContentType('')); + } + + public function testDebugActionRendersHtml() + { + $this->assertSame('text/html; charset=UTF-8', RemoteCalls::resolveContentType('debug')); + } + + public function testJsonActionsUseApplicationJson() + { + foreach (['update_settings', 'post_api_key', 'license_update', 'run_service_template_get', 'scanner__check_dir', 'update_pscan_statuses'] as $action) { + $this->assertSame( + 'application/json; charset=UTF-8', + RemoteCalls::resolveContentType($action), + $action . ' must respond as JSON' + ); + } + } + + public function testFileDownloadFallsBackToDefaultBeforeHandlerOverrides() + { + $this->assertSame('text/plain; charset=UTF-8', RemoteCalls::resolveContentType('download__quarantine_file')); + } + + public function testActionNameIsCaseInsensitive() + { + $this->assertSame('text/html; charset=UTF-8', RemoteCalls::resolveContentType('DEBUG')); + $this->assertSame('application/json; charset=UTF-8', RemoteCalls::resolveContentType('Update_Settings')); + } + + public function testNonScalarSafeResolution() + { + $this->assertSame('text/plain; charset=UTF-8', RemoteCalls::resolveContentType(null)); + } +} \ No newline at end of file