Root/
<?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\HttpFoundation; /** * Response represents an HTTP response in JSON format. * * Note that this class does not force the returned JSON content to be an * object. It is however recommended that you do return an object as it * protects yourself against XSSI and JSON-JavaScript Hijacking. * * @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside * * @author Igor Wiedler <igor@wiedler.ch> */ class JsonResponse extends Response { protected $data ; protected $callback ; /** * Constructor. * * @param mixed $data The response data * @param int $status The response status code * @param array $headers An array of response headers */ public function __construct( $data = null, $status = 200, $headers = array ()) { parent::__construct( '' , $status , $headers ); if (null === $data ) { $data = new \ArrayObject(); } $this ->setData( $data ); } /** * {@inheritdoc} */ public static function create( $data = null, $status = 200, $headers = array ()) { return new static ( $data , $status , $headers ); } /** * Sets the JSONP callback. * * @param string|null $callback The JSONP callback or null to use none * * @return JsonResponse * * @throws \InvalidArgumentException When the callback name is not valid */ public function setCallback( $callback = null) { if (null !== $callback ) { $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*+$/u' ; $parts = explode ( '.' , $callback ); foreach ( $parts as $part ) { if (!preg_match( $pattern , $part )) { throw new \InvalidArgumentException( 'The callback name is not valid.' ); } } } $this ->callback = $callback ; return $this ->update(); } /** * Sets the data to be sent as json. * * @param mixed $data * * @return JsonResponse * * @throws \InvalidArgumentException */ public function setData( $data = array ()) { // Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML. $this ->data = json_encode( $data , JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); if (JSON_ERROR_NONE !== json_last_error()) { throw new \InvalidArgumentException( $this ->transformJsonError()); } return $this ->update(); } /** * Updates the content and headers according to the json data and callback. * * @return JsonResponse */ protected function update() { if (null !== $this ->callback) { // Not using application/javascript for compatibility reasons with older browsers. $this ->headers->set( 'Content-Type' , 'text/javascript' ); return $this ->setContent(sprintf( '%s(%s);' , $this ->callback, $this ->data)); } // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) // in order to not overwrite a custom definition. if (! $this ->headers->has( 'Content-Type' ) || 'text/javascript' === $this ->headers->get( 'Content-Type' )) { $this ->headers->set( 'Content-Type' , 'application/json' ); } return $this ->setContent( $this ->data); } private function transformJsonError() { if (function_exists( 'json_last_error_msg' )) { return json_last_error_msg(); } switch (json_last_error()) { case JSON_ERROR_DEPTH: return 'Maximum stack depth exceeded.' ; case JSON_ERROR_STATE_MISMATCH: return 'Underflow or the modes mismatch.' ; case JSON_ERROR_CTRL_CHAR: return 'Unexpected control character found.' ; case JSON_ERROR_SYNTAX: return 'Syntax error, malformed JSON.' ; case JSON_ERROR_UTF8: return 'Malformed UTF-8 characters, possibly incorrectly encoded.' ; default : return 'Unknown error.' ; } } } |