diff --git a/config/jsvalidation.php b/config/jsvalidation.php index 7d8a66dc..08c25c60 100644 --- a/config/jsvalidation.php +++ b/config/jsvalidation.php @@ -39,4 +39,8 @@ */ 'remote_validation_field' => '_jsvalidation', + /* + * Whether to escape all validation messages with htmlentities. + */ + 'escape' => false, ]; diff --git a/src/Javascript/MessageParser.php b/src/Javascript/MessageParser.php index dd43ab56..c04b2747 100644 --- a/src/Javascript/MessageParser.php +++ b/src/Javascript/MessageParser.php @@ -10,14 +10,23 @@ class MessageParser { use UseDelegatedValidatorTrait; + /** + * Whether to escape messages using htmlentities. + * + * @var bool + */ + protected $escape; + /** * Create a new JsValidation instance. * * @param \Proengsoft\JsValidation\Support\DelegatedValidator $validator + * @param bool $escape */ - public function __construct(DelegatedValidator $validator) + public function __construct(DelegatedValidator $validator, $escape = false) { $this->validator = $validator; + $this->escape = $escape; } /** @@ -37,7 +46,7 @@ public function getMessage($attribute, $rule, $parameters) $this->validator->setData($data); - return $message; + return $this->escape ? e($message) : $message; } /** diff --git a/src/JsValidatorFactory.php b/src/JsValidatorFactory.php index 29a48fba..78fbcce0 100644 --- a/src/JsValidatorFactory.php +++ b/src/JsValidatorFactory.php @@ -204,7 +204,7 @@ protected function jsValidator(Validator $validator, $selector = null) $delegated = new DelegatedValidator($validator, new ValidationRuleParserProxy()); $rules = new RuleParser($delegated, $this->getSessionToken()); - $messages = new MessageParser($delegated); + $messages = new MessageParser($delegated, isset($this->options['escape']) ? $this->options['escape'] : false); $jsValidator = new ValidatorHandler($rules, $messages); diff --git a/src/Remote/Resolver.php b/src/Remote/Resolver.php index 78a62d2a..b5f26f15 100644 --- a/src/Remote/Resolver.php +++ b/src/Remote/Resolver.php @@ -21,15 +21,24 @@ class Resolver */ protected $factory; + /** + * Whether to escape validation messages. + * + * @var bool + */ + protected $escape; + /** * RemoteValidator constructor. * * @param \Illuminate\Contracts\Validation\Factory $factory + * @param bool $escape */ - public function __construct(ValidationFactory $factory) + public function __construct(ValidationFactory $factory, $escape = false) { $this->factory = $factory; $this->resolver = $this->getProtected($factory, 'resolver'); + $this->escape = $escape; } /** @@ -93,7 +102,7 @@ protected function createValidator($translator, $data, $rules, $messages, $custo public function validatorClosure() { return function ($attribute, $value, $parameters, BaseValidator $validator) { - $remoteValidator = new Validator($validator); + $remoteValidator = new Validator($validator, $this->escape); $remoteValidator->validate($value, $parameters); return $attribute; diff --git a/src/Remote/Validator.php b/src/Remote/Validator.php index f5f6b1de..26ef1912 100644 --- a/src/Remote/Validator.php +++ b/src/Remote/Validator.php @@ -26,14 +26,23 @@ class Validator */ protected $validator; + /** + * Whether to escape validation messages. + * + * @var bool + */ + protected $escape; + /** * RemoteValidator constructor. * * @param \Illuminate\Validation\Validator $validator + * @param bool $escape */ - public function __construct(BaseValidator $validator) + public function __construct(BaseValidator $validator, $escape = false) { $this->validator = $validator; + $this->escape = $escape; } /** @@ -121,7 +130,15 @@ protected function validateJsRemoteRequest($attribute, $parameters) return true; } - return $validator->messages()->get($attribute); + $messages = $validator->messages()->get($attribute); + + if ($this->escape) { + foreach ($messages as $key => $value) { + $messages[$key] = e($value); + } + } + + return $messages; } /** diff --git a/src/RemoteValidationMiddleware.php b/src/RemoteValidationMiddleware.php index 08cf1e79..026367a4 100644 --- a/src/RemoteValidationMiddleware.php +++ b/src/RemoteValidationMiddleware.php @@ -25,6 +25,13 @@ class RemoteValidationMiddleware */ protected $field; + /** + * Whether to escape messages or not. + * + * @var bool + */ + protected $escape; + /** * RemoteValidationMiddleware constructor. * @@ -35,6 +42,7 @@ public function __construct(ValidationFactory $validator, Config $config) { $this->factory = $validator; $this->field = $config->get('jsvalidation.remote_validation_field'); + $this->escape = (bool) $config->get('jsvalidation.escape', false); } /** @@ -60,7 +68,7 @@ public function handle(Request $request, Closure $next) */ protected function wrapValidator() { - $resolver = new Resolver($this->factory); + $resolver = new Resolver($this->factory, $this->escape); $this->factory->resolver($resolver->resolver($this->field)); $this->factory->extend(RemoteValidator::EXTENSION_NAME, $resolver->validatorClosure()); } diff --git a/tests/Javascript/MessageParserTest.php b/tests/Javascript/MessageParserTest.php index fcbf932c..b8bee404 100644 --- a/tests/Javascript/MessageParserTest.php +++ b/tests/Javascript/MessageParserTest.php @@ -126,4 +126,40 @@ public function testGetMessageFiles() $this->assertEquals("$attribute $rule", $message); } + + public function testEscape() + { + $attribute = 'field'; + $rule = 'Image'; + $return = ""; + + $delegated = $this->getMockBuilder(\Proengsoft\JsValidation\Support\DelegatedValidator::class) + ->disableOriginalConstructor() + ->getMock(); + + $delegated->expects($this->once()) + ->method('getData') + ->willReturn([]); + + $delegated->expects($this->once()) + ->method('hasRule') + ->with($attribute, ['Mimes', 'Image']) + ->willReturn(true); + + $delegated->expects($this->once()) + ->method('getMessage') + ->with($attribute, $rule) + ->willReturn($return); + + $delegated->expects($this->once()) + ->method('makeReplacements') + ->with($return, $attribute, $rule, []) + ->willReturn($return); + + $parser = new MessageParser($delegated, true); + + $message = $parser->getMessage($attribute, $rule, []); + + $this->assertEquals("<html>", $message); + } } diff --git a/tests/Remote/ValidatorTest.php b/tests/Remote/ValidatorTest.php index ab487532..4a02359f 100644 --- a/tests/Remote/ValidatorTest.php +++ b/tests/Remote/ValidatorTest.php @@ -51,6 +51,25 @@ public function testValidateRemoteRuleFails() } } + public function testEscapeMessage() + { + $rules = ['field' => 'active_url|required']; + $data = ['field' => 'http://nonexistentdomain']; + $params= ['false']; + $validator = $this->getRealValidator($rules, [ 'field.active_url' => '' ], $data, true); + + try { + $validator->validate('field',$params); + $this->fail(); + } catch (ValidationException $ex) { + $this->assertEquals(200, $ex->getResponse()->getStatusCode()); + $this->assertEquals('["<html>"]', $ex->getResponse()->getContent()); + } catch (HttpResponseException $ex) { + $this->assertEquals(200, $ex->getResponse()->getStatusCode()); + $this->assertEquals('["<html>"]', $ex->getResponse()->getContent()); + } + } + public function testValidateRemoteDisabled() { $rules = ['field' => 'active_url|required|alpha|no_js_validation']; @@ -107,13 +126,13 @@ protected function getRealTranslator() return $trans; } - protected function getRealValidator($rules, $messages = [], $data = []) + protected function getRealValidator($rules, $messages = [], $data = [], $escape = false) { $trans = $this->getRealTranslator(); $laravelValidator = new LaravelValidator($trans, $data, $rules, $messages); $laravelValidator->addExtension(ValidatorHandler::JSVALIDATION_DISABLE, function() { return true; }); - return new Validator($laravelValidator); + return new Validator($laravelValidator, $escape); } } diff --git a/tests/RemoteValidationMiddlewareTest.php b/tests/RemoteValidationMiddlewareTest.php index d12c2441..819cb657 100644 --- a/tests/RemoteValidationMiddlewareTest.php +++ b/tests/RemoteValidationMiddlewareTest.php @@ -29,10 +29,14 @@ public function testHandle() ->with(Validator::EXTENSION_NAME, $this->isInstanceOf('Closure')); $mockedConfig = $this->getMockForAbstractClass(\Illuminate\Contracts\Config\Repository::class, [],'',false); - $mockedConfig->expects($this->once()) + $mockedConfig->expects($this->at(0)) ->method('get') ->with('jsvalidation.remote_validation_field') ->will($this->returnValue('_jsvalidation')); + $mockedConfig->expects($this->at(1)) + ->method('get') + ->with('jsvalidation.escape', false) + ->will($this->returnValue('_jsvalidation')); $mockedRequest = $this->getMockBuilder(\Illuminate\Http\Request::class) ->disableOriginalConstructor() @@ -60,10 +64,14 @@ public function testHandleShouldNotValidate() ->getMock(); $mockedConfig = $this->getMockForAbstractClass(\Illuminate\Contracts\Config\Repository::class,[],'',false); - $mockedConfig->expects($this->once()) + $mockedConfig->expects($this->at(0)) ->method('get') ->with('jsvalidation.remote_validation_field') ->will($this->returnValue('_jsvalidation')); + $mockedConfig->expects($this->at(1)) + ->method('get') + ->with('jsvalidation.escape', false) + ->will($this->returnValue('_jsvalidation')); $mockedRequest = $this->getMockBuilder(\Illuminate\Http\Request::class) ->disableOriginalConstructor()