1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
<?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 ? json_decode($json, $assoc, $depth, $options) : null;
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');
}
}
|