diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2014-12-27 15:41:37 +0100 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2014-12-31 11:43:28 +0100 |
commit | c1f9b1f7b1b77776192048005dcc66dcf3df2bfb (patch) | |
tree | 2b38796e738dd74cb42ecd9bfd151803108386bc /includes/json | |
parent | b88ab0086858470dd1f644e64cb4e4f62bb2be9b (diff) |
Update to MediaWiki 1.24.1
Diffstat (limited to 'includes/json')
-rw-r--r-- | includes/json/FormatJson.php | 150 |
1 files changed, 132 insertions, 18 deletions
diff --git a/includes/json/FormatJson.php b/includes/json/FormatJson.php index d6116512..f3e5c76d 100644 --- a/includes/json/FormatJson.php +++ b/includes/json/FormatJson.php @@ -24,7 +24,6 @@ * JSON formatter wrapper class */ class FormatJson { - /** * Skip escaping most characters above U+007F for readability and compactness. * This encoding option saves 3 to 8 bytes (uncompressed) for each such character; @@ -56,6 +55,22 @@ class FormatJson { const ALL_OK = 3; /** + * If set, treat json objects '{...}' as associative arrays. Without this option, + * json objects will be converted to stdClass. + * The value is set to 1 to be backward compatible with 'true' that was used before. + * + * @since 1.24 + */ + const FORCE_ASSOC = 0x100; + + /** + * If set, attempts to fix invalid json. + * + * @since 1.24 + */ + const TRY_FIXING = 0x200; + + /** * Regex that matches whitespace inside empty arrays and objects. * * This doesn't affect regular strings inside the JSON because those can't @@ -96,45 +111,127 @@ class FormatJson { * (cf. FormatJson::XMLMETA_OK). Use Xml::encodeJsVar() instead in such cases. * * @param mixed $value The value to encode. Can be any type except a resource. - * @param bool $pretty If true, add non-significant whitespace to improve readability. + * @param string|bool $pretty If a string, add non-significant whitespace to improve + * readability, using that string for indentation. If true, use the default indent + * string (four spaces). * @param int $escaping Bitfield consisting of _OK class constants * @return string|bool: String if successful; false upon failure */ public static function encode( $value, $pretty = false, $escaping = 0 ) { + if ( !is_string( $pretty ) ) { + $pretty = $pretty ? ' ' : false; + } + if ( defined( 'JSON_UNESCAPED_UNICODE' ) ) { return self::encode54( $value, $pretty, $escaping ); } + return self::encode53( $value, $pretty, $escaping ); } /** - * Decodes a JSON string. + * Decodes a JSON string. It is recommended to use FormatJson::parse(), which returns more comprehensive + * result in case of an error, and has more parsing options. * * @param string $value The JSON string being decoded * @param bool $assoc When true, returned objects will be converted into associative arrays. * - * @return mixed: the value encoded in JSON in appropriate PHP type. - * `null` is returned if the JSON cannot be decoded or if the encoded data is deeper than - * the recursion limit. + * @return mixed The value encoded in JSON in appropriate PHP type. + * `null` is returned if $value represented `null`, if $value could not be decoded, + * or if the encoded data was deeper than the recursion limit. + * Use FormatJson::parse() to distinguish between types of `null` and to get proper error code. */ public static function decode( $value, $assoc = false ) { return json_decode( $value, $assoc ); } /** + * Decodes a JSON string. + * Unlike FormatJson::decode(), if $value represents null value, it will be properly decoded as valid. + * + * @param string $value The JSON string being decoded + * @param int $options A bit field that allows FORCE_ASSOC, TRY_FIXING + * @return Status If valid JSON, the value is available in $result->getValue() + */ + public static function parse( $value, $options = 0 ) { + $assoc = ( $options & self::FORCE_ASSOC ) !== 0; + $result = json_decode( $value, $assoc ); + $code = json_last_error(); + + if ( $code === JSON_ERROR_SYNTAX && ( $options & self::TRY_FIXING ) !== 0 ) { + // The most common error is the trailing comma in a list or an object. + // We cannot simply replace /,\s*[}\]]/ because it could be inside a string value. + // But we could use the fact that JSON does not allow multi-line string values, + // And remove trailing commas if they are et the end of a line. + // JSON only allows 4 control characters: [ \t\r\n]. So we must not use '\s' for matching. + // Regex match ,]<any non-quote chars>\n or ,\n] with optional spaces/tabs. + $count = 0; + $value = + preg_replace( '/,([ \t]*[}\]][^"\r\n]*([\r\n]|$)|[ \t]*[\r\n][ \t\r\n]*[}\]])/', '$1', + $value, - 1, $count ); + if ( $count > 0 ) { + $result = json_decode( $value, $assoc ); + if ( JSON_ERROR_NONE === json_last_error() ) { + // Report warning + $st = Status::newGood( $result ); + $st->warning( wfMessage( 'json-warn-trailing-comma' )->numParams( $count ) ); + return $st; + } + } + } + + switch ( $code ) { + case JSON_ERROR_NONE: + return Status::newGood( $result ); + default: + return Status::newFatal( wfMessage( 'json-error-unknown' )->numParams( $code ) ); + case JSON_ERROR_DEPTH: + $msg = 'json-error-depth'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'json-error-state-mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'json-error-ctrl-char'; + break; + case JSON_ERROR_SYNTAX: + $msg = 'json-error-syntax'; + break; + case JSON_ERROR_UTF8: + $msg = 'json-error-utf8'; + break; + case JSON_ERROR_RECURSION: + $msg = 'json-error-recursion'; + break; + case JSON_ERROR_INF_OR_NAN: + $msg = 'json-error-inf-or-nan'; + break; + case JSON_ERROR_UNSUPPORTED_TYPE: + $msg = 'json-error-unsupported-type'; + break; + } + return Status::newFatal( $msg ); + } + + /** * JSON encoder wrapper for PHP >= 5.4, which supports useful encoding options. * * @param mixed $value - * @param bool $pretty + * @param string|bool $pretty * @param int $escaping * @return string|bool */ private static function encode54( $value, $pretty, $escaping ) { + static $bug66021; + if ( $pretty !== false && $bug66021 === null ) { + $bug66021 = json_encode( array(), JSON_PRETTY_PRINT ) !== '[]'; + } + // PHP escapes '/' to prevent breaking out of inline script blocks using '</script>', // which is hardly useful when '<' and '>' are escaped (and inadequate), and such // escaping negatively impacts the human readability of URLs and similar strings. $options = JSON_UNESCAPED_SLASHES; - $options |= $pretty ? JSON_PRETTY_PRINT : 0; + $options |= $pretty !== false ? JSON_PRETTY_PRINT : 0; $options |= ( $escaping & self::UTF8_OK ) ? JSON_UNESCAPED_UNICODE : 0; $options |= ( $escaping & self::XMLMETA_OK ) ? 0 : ( JSON_HEX_TAG | JSON_HEX_AMP ); $json = json_encode( $value, $options ); @@ -142,14 +239,28 @@ class FormatJson { return false; } - if ( $pretty ) { - // Remove whitespace inside empty arrays/objects; different JSON encoders - // vary on this, and we want our output to be consistent across implementations. - $json = preg_replace( self::WS_CLEANUP_REGEX, '', $json ); + if ( $pretty !== false ) { + // Workaround for <https://bugs.php.net/bug.php?id=66021> + if ( $bug66021 ) { + $json = preg_replace( self::WS_CLEANUP_REGEX, '', $json ); + } + if ( $pretty !== ' ' ) { + // Change the four-space indent to a tab indent + $json = str_replace( "\n ", "\n\t", $json ); + while ( strpos( $json, "\t " ) !== false ) { + $json = str_replace( "\t ", "\t\t", $json ); + } + + if ( $pretty !== "\t" ) { + // Change the tab indent to the provided indent + $json = str_replace( "\t", $pretty, $json ); + } + } } if ( $escaping & self::UTF8_OK ) { $json = str_replace( self::$badChars, self::$badCharsEscaped, $json ); } + return $json; } @@ -158,7 +269,7 @@ class FormatJson { * Therefore, the missing options are implemented here purely in PHP code. * * @param mixed $value - * @param bool $pretty + * @param string|bool $pretty * @param int $escaping * @return string|bool */ @@ -187,9 +298,10 @@ class FormatJson { $json = str_replace( self::$badChars, self::$badCharsEscaped, $json ); } - if ( $pretty ) { - return self::prettyPrint( $json ); + if ( $pretty !== false ) { + return self::prettyPrint( $json, $pretty ); } + return $json; } @@ -198,9 +310,10 @@ class FormatJson { * Only needed for PHP < 5.4, which lacks the JSON_PRETTY_PRINT option. * * @param string $json + * @param string $indentString * @return string */ - private static function prettyPrint( $json ) { + private static function prettyPrint( $json, $indentString ) { $buf = ''; $indent = 0; $json = strtr( $json, array( '\\\\' => '\\\\', '\"' => "\x01" ) ); @@ -215,11 +328,11 @@ class FormatJson { ++$indent; // falls through case ',': - $buf .= $json[$i] . "\n" . str_repeat( ' ', $indent ); + $buf .= $json[$i] . "\n" . str_repeat( $indentString, $indent ); break; case ']': case '}': - $buf .= "\n" . str_repeat( ' ', --$indent ) . $json[$i]; + $buf .= "\n" . str_repeat( $indentString, --$indent ) . $json[$i]; break; case '"': $skip = strcspn( $json, '"', $i + 1 ) + 2; @@ -231,6 +344,7 @@ class FormatJson { } } $buf = preg_replace( self::WS_CLEANUP_REGEX, '', $buf ); + return str_replace( "\x01", '\"', $buf ); } } |