summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Util/Json.php
blob: 0b89dcc1a563b389c3d595f759aa0d89af7efebd (plain)
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');
    }
}