<?php /* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ namespace Icinga\Util; use Icinga\Exception\Json\JsonDecodeException; use Icinga\Exception\Json\JsonEncodeException; /** * Wrap {@link json_encode()} and {@link json_decode()} with error handling */ class Json { /** * {@link json_encode()} wrapper * * @param mixed $value * @param int $options * @param int $depth * * @return string * @throws JsonEncodeException */ public static function encode($value, $options = 0, $depth = 512) { return static::encodeAndSanitize($value, $options, $depth, false); } /** * {@link json_encode()} wrapper, automatically sanitizes bad UTF-8 * * @param mixed $value * @param int $options * @param int $depth * * @return string * @throws JsonEncodeException */ public static function sanitize($value, $options = 0, $depth = 512) { return static::encodeAndSanitize($value, $options, $depth, true); } /** * {@link json_encode()} wrapper, sanitizes bad UTF-8 * * @param mixed $value * @param int $options * @param int $depth * @param bool $autoSanitize Automatically sanitize invalid UTF-8 (if any) * * @return string * @throws JsonEncodeException */ protected static function encodeAndSanitize($value, $options, $depth, $autoSanitize) { $encoded = json_encode($value, $options, $depth); switch (json_last_error()) { case JSON_ERROR_NONE: return $encoded; /** @noinspection PhpMissingBreakStatementInspection */ case JSON_ERROR_UTF8: if ($autoSanitize) { return static::encode(static::sanitizeUtf8Recursive($value), $options, $depth); } // Fallthrough default: throw new JsonEncodeException('%s: %s', json_last_error_msg(), var_export($value, true)); } } /** * {@link json_decode()} wrapper * * @param string $json * @param bool $assoc * @param int $depth * @param int $options * * @return mixed * @throws JsonDecodeException */ public static function decode($json, $assoc = false, $depth = 512, $options = 0) { $decoded = json_decode($json, $assoc, $depth, $options); if (json_last_error() !== JSON_ERROR_NONE) { throw new JsonDecodeException('%s: %s', json_last_error_msg(), var_export($json, true)); } return $decoded; } /** * Replace bad byte sequences in UTF-8 strings inside the given JSON-encodable structure with question marks * * @param mixed $value * * @return mixed */ protected static function sanitizeUtf8Recursive($value) { switch (gettype($value)) { case 'string': return static::sanitizeUtf8String($value); case 'array': $sanitized = array(); foreach ($value as $key => $val) { if (is_string($key)) { $key = static::sanitizeUtf8String($key); } $sanitized[$key] = static::sanitizeUtf8Recursive($val); } return $sanitized; case 'object': $sanitized = array(); foreach ($value as $key => $val) { if (is_string($key)) { $key = static::sanitizeUtf8String($key); } $sanitized[$key] = static::sanitizeUtf8Recursive($val); } return (object) $sanitized; default: return $value; } } /** * Replace bad byte sequences in the given UTF-8 string with question marks * * @param string $string * * @return string */ protected static function sanitizeUtf8String($string) { return mb_convert_encoding($string, 'UTF-8', 'UTF-8'); } }