So basically jqueryphp is a jQuery plugin that allows you to call any PHP function within client-side JavaScript: jqueryphp@github
I wrote it as a response to projects such as PHP.JS and other JavaScript emulated PHP function libraries. I realize that my solution will only be useful in contexts where latency is not much of an issue since each PHP function call requires an AJAX request and response.
With that in mind, I'm extremely interested to hear if anyone thinks this plugin is a viable idea or not? And outside of the security implications, why?
Also I'm interested in any possible optimizations I could make in any of my code?
Here's the frontend: jqueryphp.js
(function( $ ) {
var config = {
'path' : 'function_request.php'
};
var methods = {
/*
* This method initializes the default path configuration
* variable required to process any function requests.
*/
init : function( options ) {
var settings = $.extend(config, options);
},
/*
* This method handles all calls to pre-existing PHP functions
* regardless of whether they are native or user defined.
*/
call : function( func, callback ) {
/*
* We subtract 2 from the arglen variable so when building
* our args string to pass to the server we are not sending
* the func string or the callback object to be interpreted
* as a argument to be passed to a PHP function.
*/
var self = this,
arglen = arguments.length - 2,
args = arguments;
/*
* Here we build a JSON object containing the arguments (if
* any) to be passed to the PHP function. We offset our index
* by 2 so we don't pass the function string or the callback
* object to the server.
*/
var jsonObj = [];
for (var i=0; i<arglen; i++) {
var argId = i + 2;
jsonObj.push( args[argId] );
}
/*
* We stringify our arguments list to pass it through to
* the server for processing.
*/
var args = JSON.stringify(jsonObj);
/*
* Finally the request object is built and sent to the server
* for handling.
*/
var request = $.ajax({
url: config.path,
type: "POST",
data: {'method': 'call', 'func': func, 'args': args},
dataType: "text",
success: function(data) {
callback(data, self);
}
});
return this;
},
/*
* This method allows for running PHP code written within
* JavaScript.
*/
exec: function( code, callback ) {
var self = this;
/*
* Finally the request object is built and sent to the server
* for handling.
*/
var request = $.ajax({
url: config.path,
type: "POST",
data: {'method': 'exec', 'code': code},
dataType: "text",
success: function(data) {
callback(data, self);
}
});
return this;
}
};
/*
* So the user of this plugin doesn't have to constantly pass in
* the 'call' parameter, being as how it will be the most used
* method, for convenience we make it so it is assumed when no
* method parameter string is passed the 'call' method is used.
*/
$.fn.php = function( method ) {
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( method !== 'init' && method !== 'exec' ) {
return methods[ 'call' ].apply( this, Array.prototype.slice.call( arguments ));
} else if ( typeof method === 'object' ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.php' );
}
};
})( jQuery );
Here's the backend: func_request.php
<?php
/*
* While the jqueryphp plugin allows for the execution of
* arbitrary code via the 'exec' method, this functionality
* is disabled by default. The plugin request 'exec' uses
* the eval() construct which is very dangerous. If it is
* used pay special attention not to pass any user provided
* data without proper validation in place.
*/
$config = array(
'EXEC' => false,
'SEC_MODE' => 'blacklist'
);
/*
* The blacklist array is used when the SEC_MODE configuration
* variable is set to blacklist. In such a case all PHP functions
* are allowed except those found in the array.
*/
$blacklist = array(
'`',
'create_function',
'escapeshellcmd',
'exec',
'include',
'include_once',
'passthru',
'pcntl_exec',
'phpinfo',
'popen',
'require',
'require_once',
'shell_exec',
'system'
);
/*
* The whitelist array is used when the EXCLUSIVE configuration
* variable is set to whitelist. In such a case all PHP functions are
* disallowed except for those found in the array.
*/
$whitelist = array(
// 'strlen', // (e.g. Allowing the strlen function)
// 'highlight_string' // (e.g. Etc...)
);
/*
* The first data passed from the client is which method request
* is being made. For instance are they making a 'call' to a PHP
* function or are they attempting to run PHP code written in
* JavaScript via 'exec'.
*/
$method_request = $_POST['method'] ? $_POST['method'] : false;
if ( $method_request ) {
switch ( $method_request ) {
/*
* This method handles all calls to pre-existing PHP functions
* regardless of whether they are native or user defined.
*/
case 'call' :
/*
* We receive the function requested and arguments that
* are to be passed to it.
*/
$func_request = $_POST['func'] ? $_POST['func'] : false;
$func_args = $_POST['args'] ? $_POST['args'] : false;
/*
* Based on the EXCLUSIVE configuration variable we attempt to
* build our function call.
*/
switch ( $config['SEC_MODE'] ) {
case 'blacklist' :
if ( function_exists($func_request)
&& !in_array($func_request, $blacklist) ) {
$function = $func_request;
} else {
$function = false;
}
break;
case 'whitelist' :
if ( function_exists($func_request)
&& in_array($func_request, $whitelist) ) {
$function = $func_request;
} else {
$function = false;
}
break;
}
/*
* Next we take our $func_args which should contain a JSON
* encoded string and convert it into a PHP associative array.
*/
$args_arr = json_decode($func_args, false);
/*
* If the user requested function exists and is allowed
* we proceed to call that function, passing any arguments
* given with the requested function.
*/
if ( $function !== false ) {
$call = $function;
echo call_user_func_array($call, $args_arr);
}
break;
/*
* This method handles the execution of user passed PHP strings
* to the server.
*/
case 'exec' :
if ( $config['EXEC'] === true ) {
/*
* We receive the code to be executed from the user.
*/
$code_string = $_POST['code'] ? $_POST['code'] : false;
echo eval( $code_string );
} else {
echo "Usage of the 'exec' method has been disabled.";
}
break;
}
}
?>
Here's a demo usage page:
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>jQuery PHP</title>
<script type="text/javascript" src="lib/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="lib/jqueryphp.js"></script>
<style>
body {
margin:40px;
}
.results {
border:1px dashed green;
width:100%;
margin-bottom:10px;
}
</style>
</head>
<script>
$(document).ready(function() {
// Initialize our path
$.fn.php('init', {'path': 'http://www.mydomain.com/jqueryphp/lib/func_request.php'});
// Example of calling the PHP strlen() function
$("#results1").php('strlen', function(data, self) {
$(self).html(data);
}, 'A test string!');
/*
* Example of calling a disabled PHP function while also
* demonstrating jQuery's method chaining is still in tact.
*/
$("#results2").php('phpinfo', function(data, self) {
$(self).html(data);
}).css({border: '1px dashed red'});
// Example of calling highlight_string()
$("#results3").php('highlight_string', function(data, self) {
$(self).html(data);
}, "<?php phpinfo(); ?>");
/*
* Example demonstrating the usage of jQuery.php's exec
* method to pass PHP code to the backend and return the
* result. This method is disabled by default.
*/
var code = "$a = 2; $b = 2; $c = $a + $b; echo $c;"
$("#results4").php('exec', code, function(data, self) {
$(self).html(data);
});
});
</script>
<body>
<p><b>jQuery.php Demonstrations:</b></p>
Returned results from the PHP strlen() function
<div id="results1" class="results"></div>
Example of calling a disabled PHP function
<div id="results2" class="results"></div>
Returned results from the PHP strlen() function
<div id="results3" class="results"></div>
Returned results using jQuery.php's 'exec' method.
<div id="results4" class="results"></div>
</body>
</html>