LUA for PHP

About

phplua is a PHP extension that enables you to embed the LUA interpreter in a PHP application. Huh? A script language embedded in a script language? This is probably what you are asking yourself now. But imagine you have an application and want to allow users to customize it using some API. PHP will be sufficient if you are the webmaster and want to extend a foreign application by a separate module. But you certainly do NOT want to give an ordinary web user of this application the ability to inject their own PHP code. PHP is simply not designed to do this. At least not in a secure way.

Of course you can restrict the available functions using disable_functions in the ini file. You could even try to parse out evil code. But you will probably never reach 100% safety.

This is where the LUA interpreter comes in. LUA has been designed as an embeddable language. You can very easily control what the user is allowed to do and what shouldn't be allowed. Furthermore it has a very low memory footprint, it's fast and it has a reasonable set of (base) features.
Using it from PHP is very easy:
$lua=new lua;
$lua->test="test";
$value=$lua->evaluate("print(test)
return 2
");
print $value[0];
This is a fork of the pecl extension "lua" and provides better integration with PHP. Merging with it again is desired :)

Differences from the original source tree

The original extension doesn't support array handling and doesn't support calling PHP functions from within LUA. These are in my opinion the most important features which were missing. Furthermore this extension allows compiling a LUA script for faster execution. Apart from that it should be more or less the same currently.

API

LUA Class API

public __construct() - Create a LUA object
public static string compile(string $chunk) - Compiles (faster execution) a LUA chunk for use with evaluate()
public mixed call_function(string $function,array $args) - Calls a LUA function from within PHP
public mixed call_table(array $table,$args) - Calls a table function (array($tablename,$functionname)) and returns the result (LUA: table.function(arg1,arg2))
public mixed call_table_self(array $table,$args) - Like call_table() but prepends self (LUA: table:function(arg1,arg2))
public mixed evaluate(string $lua_chunk) - Evaluates the LUA chunk and returns the result
public mixed evaluatefile(string $lua_file) - Evaluates the LUA chunk contained in $file and returns the result
public void expose_function(string $lua_name,mixed $callback) - exposes the callback represented by $callback to lua as function $lua_name

Limiting the default LUA sandbox

As said before LUA can be limited quite easily. Just do the following and you prevent users from having access to the filesystem:
$lua=new lua();
$lua->io=NULL;

Download

phplua-0.1.1.tar.bz2

Source Code

Can be obtained from the git repository here.

Simple Zend View Proof of concept

Well. I don't like template systems (limited functionality). I don't like the phtml approach of Zend Framework either (enables you to do dirty stuff inside the view). Here is the golden middle: LUA views in Zend Framework. A fully fledged language and it is still restricted and prevents you from doing evil stuff ;) Notice that all helpers are automagically available in LUA ;) Please also note that this was a quick hack and is not meant to be used in a production environment.
<?php
class Zend_View_Lua extends Zend_View_Abstract
{
  const TYPE_STATIC=0;
  const TYPE_LUA   =1;

  protected $_lua;
  protected $_scriptPath;
  protected $_luavars=array();

  public function __construct()
  {
    $this->_lua=new lua;
    $this->_lua->expose_function("helper",array($this,"__call"));
  }

  public function getEngine()
  {
    return $this->_lua;
  }

  public function __set($key, $val)
  {
    if (!is_object($val))
    {
      $this->_lua->$key=$val;
      $this->_luavars[$key]=true;
    }
  }

  public function __isset($key)
  {
    return (null !== $this->_lua->$key);
  }

  public function __unset($key)
  {
    $this->_lua->$key=NULL;
    unset($this->_luavars[$key]);
  }

  public function assign($spec, $value = null)
  {
    if (is_array($spec)) {
      foreach ($spec as $key => $val)
        $this->_lua->$key=$val;
      return;
    }
    $this->_lua->$spec=$value;
  }

  public function clearVars()
  {
    foreach (array_keys($this->_luavars) as $var)
      $this->_lua->$var=NULL;
    $this->_luavars=array();
  }

  private function __clone()
  {
  }

  protected function _run()
  {
    $filename=func_get_arg(0);
    if (!is_readable($filename))
      throw new Exception("File ".$filename." is not readable");

    $content=file_get_contents($filename);
    preg_match_all("/((<\?lua)\s+|\s*(\?>))/",$content,$matches,PREG_SET_ORDER | PREG_OFFSET_CAPTURE);

    $in_lua=false;
    $views=array();
    $end =array();
    $last_lua=0;
    $lua_start=NULL;
    for ($i=0;$i<sizeof($matches);$i++)
    {
      if ($matches[$i][2][0]=="<?lua")
      {
        $ends_len=sizeof($ends);
        if ($in_lua)
        {
          if ($ends_len==0)
            continue;
        }

        if ($ends_len>0)
        {
          // stupid trial and error
          for ($j=$ends_len-1;$j>=0;$j--)
          {
            $lua_script=substr($content,$lua_start+5,$ends[$j][1]-($lua_start+5));
            $compiled=lua::compile(substr($content,$lua_start+5,$ends[$j][1]-($lua_start+5)));
            if ($compiled)
            {
              $views[]=array("type"=>self::TYPE_LUA,"content"=>$compiled);
              $last_lua=$ends[$j][1]+strlen($ends[$j][0]);
              break;
            }
          }
          if ($j<0)
            throw new Exception("Parse error");
          $ends=array();
        }

        $in_lua=true;
        $lua_start=$matches[$i][0][1];
        $len=$lua_start-$last_lua;
        if ($len>0)
          $views[]=array("type"=>self::TYPE_STATIC,"content"=>substr($content,$last_lua,$len));
      }
      elseif ($in_lua && $matches[$i][3][0]=="?>")
      {
        $ends[]=$matches[$i][0];
      }
    }
    if ($in_lua)
    {
      $ends_len=sizeof($ends);
      if ($ends_len>0)
      {
        // stupid trial and error
        for ($j=$ends_len-1;$j>=0;$j--)
        {
          $lua_script=substr($content,$lua_start+5,$ends[$j][1]-($lua_start+5));
          $compiled=lua::compile(substr($content,$lua_start+5,$ends[$j][1]-($lua_start+5)));
          if ($compiled)
          {
            $views[]=array("type"=>self::TYPE_LUA,"content"=>$compiled);
            $last_lua=$ends[$j][1]+strlen($ends[$j][0]);
            break;
          }
        }
        if ($j<0)
          throw new Exception("Parse error");
        $ends=array();
      }
    }
    if ($last_lua<strlen($content)-1)
    {
      $static=substr($content,$last_lua);
      $views[]=array("type"=>self::TYPE_STATIC,"content"=>$static);
    }

    foreach ($views as $view)
    {
      if ($view["type"]==self::TYPE_STATIC)
        print $view["content"];
      else
        $this->_lua->evaluate($view["content"]);
    }
  }
}
?>

Your webdesign sucks

Yes

Bugs and wishes

By mail to andreas.streichardt _klammerÀffche_ globalpark.com