Don't silently fail if API result fails to encode
authorC. Scott Ananian <cscott@cscott.net>
Tue, 27 Nov 2018 22:47:56 +0000 (17:47 -0500)
committerC. Scott Ananian <cscott@cscott.net>
Wed, 5 Dec 2018 18:28:33 +0000 (13:28 -0500)
Ensure that errors encoding API results produce actionable log entries.

Part of the follow-up to T210550.

Change-Id: I6f311451e3b07b540f14352ce25af9d74a053d19

includes/api/ApiFormatJson.php
tests/phpunit/includes/api/format/ApiFormatJsonTest.php
tests/phpunit/includes/api/format/ApiFormatTestBase.php

index 9dcde8f..8cb22dd 100644 (file)
@@ -88,6 +88,13 @@ class ApiFormatJson extends ApiFormatBase {
                }
                $data = $this->getResult()->getResultData( null, $transform );
                $json = FormatJson::encode( $data, $this->getIsHtml(), $opt );
+               if ( $json === false ) {
+                       // This should never happen, but it's a bug which could crop up
+                       // if you use ApiResult::NO_VALIDATE for instance.
+                       // @codeCoverageIgnoreStart
+                       $this->dieDebug( __METHOD__, 'Unable to encode API result as JSON' );
+                       // @codeCoverageIgnoreEnd
+               }
 
                // T68776: OutputHandler::mangleFlashPolicy() avoids a nasty bug in
                // Flash, but what it does isn't friendly for the API, so we need to
index 2408518..4909e3c 100644 (file)
@@ -128,6 +128,37 @@ class ApiFormatJsonTest extends ApiFormatTestBase {
 
                                // Cross-domain mangling
                                [ [ '< Cross-Domain-Policy >' ], '["\u003C Cross-Domain-Policy >"]' ],
+
+                               // Invalid UTF-8: bytes 192, 193, and 245-255 are off-limits
+                               [
+                                       [ 'foo' => "\xFF" ],
+                                       "{\"foo\":\"\u{FFFD}\"}", // Mangled when validated (T210548)
+                               ],
+                               [
+                                       [ 'foo' => "\xFF" ],
+                                       new MWException(
+                                               'Internal error in ApiFormatJson::execute: ' .
+                                               'Unable to encode API result as JSON'
+                                       ),
+                                       [],
+                                       [ 'flags' => ApiResult::NO_VALIDATE ],
+                               ],
+                               // NaN is also not allowed
+                               [
+                                       [ 'foo' => NAN ],
+                                       new InvalidArgumentException(
+                                               'Cannot add non-finite floats to ApiResult'
+                                       ),
+                               ],
+                               [
+                                       [ 'foo' => NAN ],
+                                       new MWException(
+                                               'Internal error in ApiFormatJson::execute: ' .
+                                               'Unable to encode API result as JSON'
+                                       ),
+                                       [],
+                                       [ 'flags' => ApiResult::NO_VALIDATE ],
+                               ],
                        ] )
                        // @todo Test rawfm
                );
index ca20cb6..27ec73a 100644 (file)
@@ -38,6 +38,7 @@ abstract class ApiFormatTestBase extends MediaWikiTestCase {
                        $options = [ 'class' => $options ];
                }
                $printerName = $options['name'] ?? $this->printerName;
+               $flags = $options['flags'] ?? 0;
 
                $context = new RequestContext;
                $context->setRequest( new FauxRequest( $params, true ) );
@@ -49,7 +50,7 @@ abstract class ApiFormatTestBase extends MediaWikiTestCase {
                $result = $main->getResult();
                $result->addArrayType( null, 'default' );
                foreach ( $data as $k => $v ) {
-                       $result->addValue( null, $k, $v );
+                       $result->addValue( null, $k, $v, $flags );
                }
 
                $ret = [];