Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\HttpFoundation;
13
 
14
/**
15
 * Response represents an HTTP response in JSON format.
16
 *
17
 * Note that this class does not force the returned JSON content to be an
18
 * object. It is however recommended that you do return an object as it
19
 * protects yourself against XSSI and JSON-JavaScript Hijacking.
20
 *
21
 * @see https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/AJAX_Security_Cheat_Sheet.md#always-return-json-with-an-object-on-the-outside
22
 *
23
 * @author Igor Wiedler <igor@wiedler.ch>
24
 */
25
class JsonResponse extends Response
26
{
27
    protected $data;
28
    protected $callback;
29
 
30
    // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML.
31
    // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
32
    const DEFAULT_ENCODING_OPTIONS = 15;
33
 
34
    protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS;
35
 
36
    /**
37
     * @param mixed $data    The response data
38
     * @param int   $status  The response status code
39
     * @param array $headers An array of response headers
40
     * @param bool  $json    If the data is already a JSON string
41
     */
42
    public function __construct($data = null, int $status = 200, array $headers = [], bool $json = false)
43
    {
44
        parent::__construct('', $status, $headers);
45
 
46
        if (null === $data) {
47
            $data = new \ArrayObject();
48
        }
49
 
50
        $json ? $this->setJson($data) : $this->setData($data);
51
    }
52
 
53
    /**
54
     * Factory method for chainability.
55
     *
56
     * Example:
57
     *
58
     *     return JsonResponse::create(['key' => 'value'])
59
     *         ->setSharedMaxAge(300);
60
     *
61
     * @param mixed $data    The JSON response data
62
     * @param int   $status  The response status code
63
     * @param array $headers An array of response headers
64
     *
65
     * @return static
66
     *
67
     * @deprecated since Symfony 5.1, use __construct() instead.
68
     */
69
    public static function create($data = null, int $status = 200, array $headers = [])
70
    {
71
        trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, \get_called_class());
72
 
73
        return new static($data, $status, $headers);
74
    }
75
 
76
    /**
77
     * Factory method for chainability.
78
     *
79
     * Example:
80
     *
81
     *     return JsonResponse::fromJsonString('{"key": "value"}')
82
     *         ->setSharedMaxAge(300);
83
     *
84
     * @param string|null $data    The JSON response string
85
     * @param int         $status  The response status code
86
     * @param array       $headers An array of response headers
87
     *
88
     * @return static
89
     */
90
    public static function fromJsonString(string $data = null, int $status = 200, array $headers = [])
91
    {
92
        return new static($data, $status, $headers, true);
93
    }
94
 
95
    /**
96
     * Sets the JSONP callback.
97
     *
98
     * @param string|null $callback The JSONP callback or null to use none
99
     *
100
     * @return $this
101
     *
102
     * @throws \InvalidArgumentException When the callback name is not valid
103
     */
104
    public function setCallback(string $callback = null)
105
    {
106
        if (null !== $callback) {
107
            // partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/
108
            // partially taken from https://github.com/willdurand/JsonpCallbackValidator
109
            //      JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details.
110
            //      (c) William Durand <william.durand1@gmail.com>
111
            $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u';
112
            $reserved = [
113
                'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while',
114
                'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super',  'const', 'export',
115
                'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false',
116
            ];
117
            $parts = explode('.', $callback);
118
            foreach ($parts as $part) {
119
                if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) {
120
                    throw new \InvalidArgumentException('The callback name is not valid.');
121
                }
122
            }
123
        }
124
 
125
        $this->callback = $callback;
126
 
127
        return $this->update();
128
    }
129
 
130
    /**
131
     * Sets a raw string containing a JSON document to be sent.
132
     *
133
     * @return $this
134
     *
135
     * @throws \InvalidArgumentException
136
     */
137
    public function setJson(string $json)
138
    {
139
        $this->data = $json;
140
 
141
        return $this->update();
142
    }
143
 
144
    /**
145
     * Sets the data to be sent as JSON.
146
     *
147
     * @param mixed $data
148
     *
149
     * @return $this
150
     *
151
     * @throws \InvalidArgumentException
152
     */
153
    public function setData($data = [])
154
    {
155
        try {
156
            $data = json_encode($data, $this->encodingOptions);
157
        } catch (\Exception $e) {
158
            if ('Exception' === \get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
159
                throw $e->getPrevious() ?: $e;
160
            }
161
            throw $e;
162
        }
163
 
164
        if (\PHP_VERSION_ID >= 70300 && (\JSON_THROW_ON_ERROR & $this->encodingOptions)) {
165
            return $this->setJson($data);
166
        }
167
 
168
        if (\JSON_ERROR_NONE !== json_last_error()) {
169
            throw new \InvalidArgumentException(json_last_error_msg());
170
        }
171
 
172
        return $this->setJson($data);
173
    }
174
 
175
    /**
176
     * Returns options used while encoding data to JSON.
177
     *
178
     * @return int
179
     */
180
    public function getEncodingOptions()
181
    {
182
        return $this->encodingOptions;
183
    }
184
 
185
    /**
186
     * Sets options used while encoding data to JSON.
187
     *
188
     * @return $this
189
     */
190
    public function setEncodingOptions(int $encodingOptions)
191
    {
192
        $this->encodingOptions = $encodingOptions;
193
 
194
        return $this->setData(json_decode($this->data));
195
    }
196
 
197
    /**
198
     * Updates the content and headers according to the JSON data and callback.
199
     *
200
     * @return $this
201
     */
202
    protected function update()
203
    {
204
        if (null !== $this->callback) {
205
            // Not using application/javascript for compatibility reasons with older browsers.
206
            $this->headers->set('Content-Type', 'text/javascript');
207
 
208
            return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
209
        }
210
 
211
        // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
212
        // in order to not overwrite a custom definition.
213
        if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
214
            $this->headers->set('Content-Type', 'application/json');
215
        }
216
 
217
        return $this->setContent($this->data);
218
    }
219
}