Skip to content

Instantly share code, notes, and snippets.

@ptz0n
Created September 14, 2011 16:51
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ptz0n/1217080 to your computer and use it in GitHub Desktop.
Save ptz0n/1217080 to your computer and use it in GitHub Desktop.
Validate JSONP Callback
<?php
/**
* Validate JSONP Callback
*
* https://github.com/tav/scripts/blob/master/validate_jsonp.py
* https://github.com/talis/jsonp-validator/blob/master/src/main/java/com/talis/jsonp/JsonpCallbackValidator.java
* http://tav.espians.com/sanitising-jsonp-callback-identifiers-for-security.html
* http://news.ycombinator.com/item?id=809291
*
* ^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$
*
*/
class Jsonp {
/**
* Validation tests
*
* @var array
*
* @access private
*/
private $_tests = array(
'' => false,
'hello' => true,
'alert()' => false,
'test()' => false,
'a-b' => false,
'23foo' => false,
'foo23' => true,
'$210' => true,
'_bar' => true,
'some_var' => true,
'$' => true,
'somevar' => true,
'function' => false,
' somevar' => false,
'$.ajaxHandler' => true,
'$.23' => false,
'array_of_functions[42]' => true,
'array_of_functions[42][1]' => true,
'$.ajaxHandler[42][1].foo' => true,
'array_of_functions[42]foo[1]' => false,
'array_of_functions[]' => false,
'array_of_functions["key"]' => true,
'myFunction[123].false' => false,
'myFunction .tester' => false,
'_function' => true,
'petersCallback1412331422[12]' => true,
':myFunction' => false
);
/**
* Is valid callback
*
* @param string $callback
*
* @return boolean
*/
function isValidCallback($callback)
{
$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'
);
foreach(explode('.', $callback) as $identifier) {
if(!preg_match('/^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/', $identifier)) {
return false;
}
if(in_array($identifier, $reserved)) {
return false;
}
}
return true;
}
/**
* Test callback strings
*
* @param string $callback
*
* @return void
*
* @access private
*/
private function _test($callback, $valid)
{
$vocal = $valid ? 'valid' : 'invalid';
if($this->isValidCallback($callback) === $valid) {
echo '"'.$callback.'" <span style="color:green">passed as '.$vocal.'</span>.', "\n";
return true;
}
else {
echo '"'.$callback.'" <span style="color:red;font-weight:700;">failed as '.$vocal.'</span>.', "\n";
return false;
}
}
/**
* Run all tests
*
* @return void
*
* @access public
*/
function runTests()
{
echo '<strong>Testing ', count($this->_tests), ' callback methods:</strong>', "\n\n";
$passed = 0;
foreach($this->_tests as $callback => $valid) {
$passed = self::_test($callback, $valid) ? $passed+1 : $passed;
}
echo "\n", $passed, ' of ', count($this->_tests), ' tests passed.';
}
}
$jsonp = new Jsonp();
echo '<pre>';
$jsonp->runTests();
@ptz0n
Copy link
Author

ptz0n commented Sep 16, 2011

Testing 27 callback methods:

"" passed as invalid.
"hello" passed as valid.
"alert()" passed as invalid.
"test()" passed as invalid.
"a-b" passed as invalid.
"23foo" passed as invalid.
"foo23" passed as valid.
"$210" passed as valid.
"_bar" passed as valid.
"some_var" passed as valid.
"$" passed as valid.
"somevar" passed as valid.
"function" passed as invalid.
" somevar" passed as invalid.
"$.ajaxHandler" passed as valid.
"$.23" passed as invalid.
"array_of_functions[42]" passed as valid.
"array_of_functions[42][1]" passed as valid.
"$.ajaxHandler[42][1].foo" passed as valid.
"array_of_functions[42]foo[1]" passed as invalid.
"array_of_functions[]" passed as invalid.
"array_of_functions["key"]" passed as valid.
"myFunction[123].false" passed as invalid.
"myFunction .tester" passed as invalid.
"_function" passed as valid.
"petersCallback1412331422[12]" passed as valid.
":myFunction" passed as invalid.

27 of 27 tests passed.

Thanks to Tobias Sjösten for updated regex.

@Daniel15
Copy link

Daniel15 commented Jul 9, 2012

Thanks for that, here's a C# port of this code: https://gist.github.com/3074365

@ptrm
Copy link

ptrm commented May 17, 2013

Many thanks. And just in case, here's a one-liner for that:

^(?(?!(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)\b)(?<pat>[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|'.+'|\d+)\])*?))(\.(?P>pat))*$

@braco
Copy link

braco commented Aug 14, 2013

This regex doesn't seem to allow "foo.bar"?

This might be better:

/^([a-zA-Z_$][0-9a-zA-Z_$](?:.[0-9a-zA-Z$]+)?(?:[(?:"".+""|'.+'|\d+)])_?){1,}$/

test here:

http://burkeware.com/software/regex_playground.html

@Daniel15
Copy link

@braco It splits by . and then checks every segment individually, so will allow foo.bar.

@fyrye
Copy link

fyrye commented Jan 23, 2014

eval should probably also be added to the reserved list.

@tuhuynh27
Copy link

Thanks, @ptz0n, here's a Java version of the above code: https://gist.github.com/tuhuynh27/e70e2b517712cdbdbc1c6251df35409a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment