2015-08-17 17:00:26 -07:00
< ? 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 ;
2015-08-27 12:03:05 -07:00
2017-02-02 16:28:38 -08:00
// Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML.
2015-08-27 12:03:05 -07:00
// 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
2018-11-23 12:29:20 +00:00
const DEFAULT_ENCODING_OPTIONS = 15 ;
protected $encodingOptions = self :: DEFAULT_ENCODING_OPTIONS ;
2015-08-17 17:00:26 -07:00
/**
* @ param mixed $data The response data
* @ param int $status The response status code
* @ param array $headers An array of response headers
2018-11-23 12:29:20 +00:00
* @ param bool $json If the data is already a JSON string
2015-08-17 17:00:26 -07:00
*/
2018-11-23 12:29:20 +00:00
public function __construct ( $data = null , $status = 200 , $headers = array (), $json = false )
2015-08-17 17:00:26 -07:00
{
parent :: __construct ( '' , $status , $headers );
if ( null === $data ) {
$data = new \ArrayObject ();
}
2018-11-23 12:29:20 +00:00
$json ? $this -> setJson ( $data ) : $this -> setData ( $data );
2015-08-17 17:00:26 -07:00
}
/**
2017-02-02 16:28:38 -08:00
* Factory method for chainability .
*
* Example :
*
* return JsonResponse :: create ( $data , 200 )
* -> setSharedMaxAge ( 300 );
*
* @ param mixed $data The json response data
* @ param int $status The response status code
* @ param array $headers An array of response headers
*
* @ return static
2015-08-17 17:00:26 -07:00
*/
public static function create ( $data = null , $status = 200 , $headers = array ())
{
return new static ( $data , $status , $headers );
}
2018-11-23 12:29:20 +00:00
/**
* Make easier the creation of JsonResponse from raw json .
*/
public static function fromJsonString ( $data = null , $status = 200 , $headers = array ())
{
return new static ( $data , $status , $headers , true );
}
2015-08-17 17:00:26 -07:00
/**
* Sets the JSONP callback .
*
* @ param string | null $callback The JSONP callback or null to use none
*
2017-02-02 16:28:38 -08:00
* @ return $this
2015-08-17 17:00:26 -07:00
*
* @ throws \InvalidArgumentException When the callback name is not valid
*/
public function setCallback ( $callback = null )
{
if ( null !== $callback ) {
2018-11-23 12:29:20 +00:00
// partially taken from http://www.geekality.net/2011/08/03/valid-javascript-identifier/
// partially taken from https://github.com/willdurand/JsonpCallbackValidator
2017-02-02 16:28:38 -08:00
// JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details.
// (c) William Durand <william.durand1@gmail.com>
$pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u' ;
$reserved = array (
'break' , 'do' , 'instanceof' , 'typeof' , 'case' , 'else' , 'new' , 'var' , 'catch' , 'finally' , 'return' , 'void' , 'continue' , 'for' , 'switch' , 'while' ,
'debugger' , 'function' , 'this' , 'with' , 'default' , 'if' , 'throw' , 'delete' , 'in' , 'try' , 'class' , 'enum' , 'extends' , 'super' , 'const' , 'export' ,
'import' , 'implements' , 'let' , 'private' , 'public' , 'yield' , 'interface' , 'package' , 'protected' , 'static' , 'null' , 'true' , 'false' ,
);
2015-08-17 17:00:26 -07:00
$parts = explode ( '.' , $callback );
foreach ( $parts as $part ) {
2018-11-23 12:29:20 +00:00
if ( ! preg_match ( $pattern , $part ) || \in_array ( $part , $reserved , true )) {
2015-08-17 17:00:26 -07:00
throw new \InvalidArgumentException ( 'The callback name is not valid.' );
}
}
}
$this -> callback = $callback ;
return $this -> update ();
}
2018-11-23 12:29:20 +00:00
/**
* Sets a raw string containing a JSON document to be sent .
*
* @ param string $json
*
* @ return $this
*
* @ throws \InvalidArgumentException
*/
public function setJson ( $json )
{
$this -> data = $json ;
return $this -> update ();
}
2015-08-17 17:00:26 -07:00
/**
* Sets the data to be sent as JSON .
*
* @ param mixed $data
*
2017-02-02 16:28:38 -08:00
* @ return $this
2015-08-17 17:00:26 -07:00
*
* @ throws \InvalidArgumentException
*/
public function setData ( $data = array ())
{
2018-11-23 12:29:20 +00:00
if ( \defined ( 'HHVM_VERSION' )) {
2015-08-27 12:03:05 -07:00
// HHVM does not trigger any warnings and let exceptions
// thrown from a JsonSerializable object pass through.
// If only PHP did the same...
$data = json_encode ( $data , $this -> encodingOptions );
} else {
2018-11-23 12:29:20 +00:00
if ( ! interface_exists ( 'JsonSerializable' , false )) {
set_error_handler ( function () { return false ; });
try {
2015-08-27 12:03:05 -07:00
$data = @ json_encode ( $data , $this -> encodingOptions );
2018-11-23 12:29:20 +00:00
} finally {
2015-08-27 12:03:05 -07:00
restore_error_handler ();
}
2018-11-23 12:29:20 +00:00
} else {
try {
$data = json_encode ( $data , $this -> encodingOptions );
} catch ( \Exception $e ) {
if ( 'Exception' === \get_class ( $e ) && 0 === strpos ( $e -> getMessage (), 'Failed calling ' )) {
throw $e -> getPrevious () ? : $e ;
}
throw $e ;
2015-08-27 12:03:05 -07:00
}
2015-08-17 17:00:26 -07:00
}
}
if ( JSON_ERROR_NONE !== json_last_error ()) {
2016-04-20 09:56:34 -07:00
throw new \InvalidArgumentException ( json_last_error_msg ());
2015-08-17 17:00:26 -07:00
}
2018-11-23 12:29:20 +00:00
return $this -> setJson ( $data );
2015-08-17 17:00:26 -07:00
}
/**
* Returns options used while encoding data to JSON .
*
* @ return int
*/
public function getEncodingOptions ()
{
return $this -> encodingOptions ;
}
/**
* Sets options used while encoding data to JSON .
*
* @ param int $encodingOptions
*
2017-02-02 16:28:38 -08:00
* @ return $this
2015-08-17 17:00:26 -07:00
*/
public function setEncodingOptions ( $encodingOptions )
{
$this -> encodingOptions = ( int ) $encodingOptions ;
return $this -> setData ( json_decode ( $this -> data ));
}
/**
* Updates the content and headers according to the JSON data and callback .
*
2017-02-02 16:28:38 -08:00
* @ return $this
2015-08-17 17:00:26 -07:00
*/
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 );
}
}