annotate vendor/symfony/http-foundation/JsonResponse.php @ 19:fa3358dc1485 tip

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