Source

trivial-remote-objects / php / server.php

Full commit
<?
/* 

Copyright (c) 2011, Toby Davies
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
      
* Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
*/


/*
 * JSON RPC Framework Server Side code 
 * 
 * calling rpcServer($functions); handles one RPC request per HTTP request. 
 *
 * Usage: 
 * rpcServer(array("someRPCFunctionDefinedInThisFile"=>False,
 *                 "someAnonFunc"=>function{ ... }, //requires PHP>=5.3.0
 *                 "someRPCFunction"=>"some/file/defining-or-requred-by/the/RPCFunc.php", 
 *                   ...));
 *
 * functions should return normal PHP values - strings/numbers/arrays/objects-and-assocs
 * the RPC code serializes them and deserializes them as Javascript equivalents 
 * (strings/numbers/arrays/objects respectively,
 *  note only public members of objects will be sent to the client)
 * it is important NOT to echo anything in an RPC func, it will break the RPC protocol format!
 *
 * Request Format:
 * POST application/x-www-form-urlencoded variables:
 *  'f' - the name of the function to be called
 *  'a' - a JSON encoded array of arguments
 *
 * Response Format:
 * {error:"Error Message As JSON Encoded String"}
 * or
 * {success:{some:"JSON", encoded:["object"]}
 *
 */

//handle errors and exceptions such that they can be caught client side.
function rpcError($errno,$errstring,$errfile=NULL,$errline=NULL){
  if(error_reporting()){
    die(json_encode(array('error'=>$errstring." :: $errfile @ $errline")));
  }
  return true;
}

//Use the associative array $rpcs (functionName => fileName) or (functionName=>anonFunc)
//(allows call-time loading of subsets of functions)
//$func=$_POST['f'] is the rpc function name to use. 
//$file=$rpc[$func] is the file in which the function named $func lives 
// (if False, function $func already loaded)
//$_POST['a'] is a JSON encoded array to apply to $func
//see rpc.js for client-side usage.
function rpcServer($rpcs){
  header("Content-Type: application/json");
  set_error_handler('rpcError');
  try{
    $f=($postp=($_SERVER['REQUEST_METHOD']=='POST'))?$_POST['f']:'array_keys';
    $args=($postp?json_decode((get_magic_quotes_gpc())?
			      stripslashes($_POST['a']):$_POST['a']):array($rpcs));
    if($postp && !isset($rpcs[$f])) rpcError(-1,"Undefined Function $f");
    if($postp && ($file=$rpcs[$f]) && is_string($file)) include_once $file; //import necessary file if $f requires it
    if($postp && !is_string($file) && is_callable($file)) $f=$file; //if $rpcs[$f] is an anonymous function, use that
    $result=array('success'=>call_user_func_array($f,$args));
  }catch (Exception $e){
    $result=array('error'=>$e->getMessage()); // catch and encode any exceptions in the JSON RPC format described above
  }
  echo json_encode($result);
}


/*
  Utility Functions for composing safe query based RPCs
*/

$qProcessors=array(); // store result types available

//escape args and insert them into the the appropriate placeholders
// '?' is replaced with the next numeric arg
// {argName}  in replaced with the value of the key 'argName' in $args
// this form also works for {n} for some integer n
function query($pattern,$db,$args=array(),$resultType=NULL){
  global $qProcessors;

  //initialize result type dependant on query
  if($resultType==NULL){
    if(preg_match('/^\s*(SELECT|SHOW|DESCRIBE|EXPLAIN)\b/i',$pattern)){
      $resultType="assocs";
    }else if(preg_match('/^\s*INSERT\b/i',$pattern)){
      $resultType='insert_id';
    }else{
      $resultType="count";
    }
  }

  foreach ($args as $argName => $argVal){
    if(is_int($argName)){
      //replace ? with $argVal
      $pattern = preg_replace("/\\?/",mysql_real_escape_string("$argVal"),$pattern,1);
    }
    //replace {$argname} with $argVal
    $pattern = preg_replace("/\\{".preg_quote("$argName")."\\}/",mysql_real_escape_string("$argVal"),$pattern);
  }
  //  echo $pattern;
  $res=mysql_query($pattern);
  if(($e=mysql_error())) 
    throw new Exception($e);
  // use the appropriate query processor to return the result, or false if query failed
  return $res?call_user_func($qProcessors[$resultType],$res):$res;
}

//return a single value from a result set
$qProcessors['value']="result_value";
function result_value($res){
  list($r)=mysql_fetch_row($res);
  return $r;
}

//return a single row as an integer indexed array
$qProcessors['list']="result_list";
function result_list($res){
  return mysql_fetch_row($res);
}

//return a single row as an associative array
$qProcessors['assoc']="result_assoc";
function result_assoc($res){
  return mysql_fetch_assoc($res);
}

//return a list of values (single element rows)
$qProcessors['values']="result_values";
function result_values($res){
  $rows=array();
  while((list($row)=mysql_fetch_row($res)))
    $rows[]=$row;
  return $rows;
}

//return anarray of 0-indexed rows
$qProcessors['lists']="result_lists";
function result_lists($res){
  $rows=array();
  while(($row=mysql_fetch_row($res)))
    $rows[]=$row;
  return $rows;
}

//return an array of assocs
$qProcessors['assocs']="result_assocs";
function result_assocs($res){
  $rows=array();
  while(($row=mysql_fetch_assoc($res)))
    $rows[]=$row;
  return $rows;
}

//return the number of rows affected
$qProcessors['count']="result_count";
function result_count($res){
  return $res===True?mysql_affected_rows():mysql_num_rows();
}

//return the number of rows returned/modified
$qProcessors['pairs']="result_pairs";
function result_pairs($res){
  $rows=array();
  while(($row=mysql_fetch_row($res)))
    $rows[$row[0]]=$row[1];
  return $rows;
}

//return the autoincrement ID of the last row inserted
$qProcessors['insert_id']="result_insert_id";
function result_insert_id($res){
  return mysql_insert_id();
}

?>